4.1 UnityShaderVariables.cginc文件中的着色器常量和函数
4.1.1 进行变换操作用的矩阵
1.判断USING DIRECTIONAL LIGTH宏是否定义并分析与立体渲染相关的宏
立体多例化渲染技术的核心思想是一次向渲染管道上提交两份待渲染的几何体数据,减少绘制调用(draw call)的次数,提升渲染性能。
单程立体渲染(single pass stereo rendering)相关的预处理宏UNITY_SINGLE_PASS_STEREO、立体多例化渲染(stereo instancing rendering)预处理宏UNITY_STEREO_INSTANCING_ENABLED、多视角立体渲染(multi-view stereo rendering)预处理宏UNITY_STEREO_MULTIVIEW_ENABLED的启用情况决定宏USING_STEREO_MATRICES是否开启。
如果上面3个宏有任意一个是开启的,那么宏USING_STEREO_MATRICES即开启,表示要使用与立体渲染相关的矩阵。
2.和立体渲染相关的一系列矩阵1
下面的代码用于定义一系列与立体渲染相关的矩阵。
// 所在文件:UnityShaderVariables.cginc代码
// 所在目录:CGIncludes
// 从原文件第16行开始,至第27行结束
// 如果要使用和立体渲染相关的矩阵
#if defined(USING_STEREO_MATRICES)
#define glstate_matrix_projection unity_StereoMatrixP[unity_StereoEyeIndex]
#define unity_MatrixV unity_StereoMatrixV[unity_StereoEyeIndex]
#define unity_MatrixInvV unity_StereoMatrixInvV[unity_StereoEyeIndex]
#define unity_MatrixVP unity_StereoMatrixVP[unity_StereoEyeIndex]
#define unity_CameraProjection unity_StereoCameraProjection[unity_StereoEyeIndex]
#define unity_CameraInvProjection \
unity_StereoCameraInvProjection[unity_StereoEyeIndex]
#define unity_WorldToCamera unity_StereoWorldToCamera[unity_StereoEyeIndex]
#define unity_CameraToWorld unity_StereoCameraToWorld[unity_StereoEyeIndex]
#define _WorldSpaceCameraPos \
unity_StereoWorldSpaceCameraPos[unity_StereoEyeIndex]
#endif
3.和立体渲染相关的一系列矩阵2
带有unity_Stereo前缀的变量和unity_StereoEyeIndex也定义在UnityShaderVariables.cginc文件中。
4.UNITY DECLARE MULTIVIEW宏的定义
代码中的UNITY_DECLARE_MULTIVIEW在HLSLSupport.cginc文件中定义,展开如下。
// 所在文件:HLSLSupport.cginc代码
// 所在目录:CGIncludes
// 从原文件第262行开始,至第262行结束
#define UNITY_DECLARE_MULTIVIEW(number_of_views) \
GLOBAL_CBUFFER_START(OVR_multiview)\
uintgl_ViewID;\
uintnumViews_##number_of_views;\
GLOBAL_CBUFFER_END
4.1.2 和摄像机相关的常量缓冲区
1.常量缓冲区UnityPerCamera的定义
// 所在文件:UnityShaderVariables.cginc代码
// 所在目录:CGIncludes
// 从原文件第45行开始,至第85行结束
// D3D11把所有的shader变量组织进constant buffer中去。大部分U3D内
// 建shader变量也组织进constant buffer中去,用户自定义的shader变
// 量也可以组织进constant buffer中去。定义一个constant buffer以存储
// shader变量,如下面的定义方式:
// CBUFFER_START(MyRarelyUpdatedVariables)
// float4 _SomeGlobalValue;
// CBUFFER_END
// Unity 3D内建的,用来传递给每个摄像机的参数组
// 这些参数由引擎从C#层代码传递给着色器
CBUFFER_START(UnityPerCamera)
float4_Time; // 从载入当前的scene开始算起流逝的时间值,单位是s。其x、y、z、w分量分别对
// 应为1/20流逝时间值、流逝时间值、2倍流逝时间值、3倍流逝时间值
float4_SinTime; // _Time值的正弦值,其x、y、z、w分别对应于1/8的当前流逝时间值的正弦值、
// 1/4的当前流逝时间值的正弦值、1/2的当前流逝时间值的正弦值、当前流逝时间
// 值的正弦值
float4_CosTime; // _Time值的余弦值,其x、y、z、w分别对应于1/8的当前流逝时间值的余弦值、
// 1/4的当前流逝时间值的余弦值、1/2的当前流逝时间值的余弦值、当前流逝时间
// 值的余弦值
float4unity_DeltaTime; // 本帧到上一帧过去的时间间隔
// 如果没有定义开启单程立体渲染,没有开启立体多例化渲染,就由引擎C#层代码传递一个表征当前摄像机
// 在世界空间中的坐标值
#if !defined(UNITY_SINGLE_PASS_STEREO)
&& !defined(STEREO_INSTANCING_ON)
float3_WorldSpaceCameraPos;
#endif
// 投影矩阵相关的参数,x为1或者-1,y为近截平面值,z为远截平面值,w为远
// 截平面值的倒数
float4_ProjectionParams;
// 视口相关的参数,x为视口宽度,y为视口高度,z为1加上视口宽度的倒数,w为1加上视口高度的倒数
float4_ScreenParams;
// 用来线性化Z buffer
// x分量为1减去视截体远截面值与视锥近截面值的商,y分量为视截体远截面值与
// 视锥近截面值的商,z分量为x分量除以视截体远截面值,w分量为y分量除以视截体远截面值
float4_ZBufferParams;
// x分量为正交投影摄像机的宽度,y分量为正交投影摄像机的高度
// z分量未使用,w分量当摄像机为正交投影时为1,透视投影为0
float4unity_OrthoParams;
CBUFFER_END
2. 常量缓冲区UnityPerCameraRare的定义
// 所在文件:UnityShaderVariables.cginc代码
// 所在目录:CGIncludes
// 从原文件第88行开始,至第100行结束
CBUFFER_START(UnityPerCameraRare)
// 当前摄像机视截体(view frustum)的6个截平面的平面表达式。这些平面表达式在世界坐标系下描述。
// 每个平面表达式用方程ax+by+cz+d = 0表达。float4中的分量x、y、z、w依次存储了系数a、b、c、d。
// 6个平面依次是左、右、下、上、近、远裁剪平面
float4unity_CameraWorldClipPlanes[6];
//如果不使用立体渲染,各种矩阵变量就是一个单变量而不是两个变量的数组
#if !defined(USING_STEREO_MATRICES)
// 当前摄像机的投影矩阵
float4x4unity_CameraProjection;
// 当前摄像机的投影矩阵的逆矩阵
float4x4unity_CameraInvProjection;
// 当前摄像机的观察矩阵
float4x4unity_WorldToCamera;
// 当前摄像机的观察矩阵的逆矩阵
float4x4unity_CameraToWorld;
#endif
CBUFFER_END
4.1.3 与光照相关的工具函数和内置光源
1.变量WorldSpaceLightPos0的定义
// 所在文件:UnityShaderVariables.cginc代码
// 所在目录:CGIncludes
// 从原文件第106行开始,至第112行结束
CBUFFER_START(UnityLighting)
#ifdef USING_DIRECTIONAL_LIGHT // 有向平行光
// 分量x、y、z存储的是有向平行光的方向向量
half4_WorldSpaceLightPos0;
#else //如果不是有向平行光,那么分量x、y、z存储的是光源在世界空间中的位置坐标
float4_WorldSpaceLightPos0;
#endif
2. 4个非重要点光源的位置、衰减值和照射范围
// 所在文件:UnityShaderVariables.cginc代码
// 所在目录:CGIncludes
// 从原文件第114行开始,至第123行结束
float4_LightPositionRange; // x、y、z分量为光源的位置,w分量为光源照射范围的倒数
// 4个光源的x、y、z坐标,注意unity_4LightPosX0中的4个分
// 量分别存储了4个光源的x坐标,unity_4LightPosY0中的4个
// 分量分别存储了4个光源的y坐标,其余类推
float4unity_4LightPosX0;
float4unity_4LightPosY0;
float4unity_4LightPosZ0;
half4unity_4LightAtten0; // 4个光源的衰减值
上面代码定义的4个光源,是等级为非重要光源的点光源。这4个光源仅用在前向渲染途径的base pass中。
3. 8个光源的颜色、位置、衰减值和照射方向
通过以下代码定义8个光源的颜色、位置、衰减值和照射方向。
// 所在文件:UnityShaderVariables.cginc代码
// 所在目录:CGIncludes
// 从原文件第124行开始,至第130行结束
half4unity_LightColor[8]; // 8个光源的颜色
//有向光的视图空间顶点光源位置(position,1),或者(-direction,0)
// 在观察空间中用来在顶点着色器中执行顶点光照计算的光源位置点。如果光源是有向平行光。那么变量中
// 的x、y、z分量存储着光源的光照射方向的反方向,w分量为0;如果光源是非有向平行光源,那么变量
// 中的x、y、z分量存储着光源的位置坐标,w分量为1
float4unity_LightPosition[8];
//对于非点光源x = cos(spotAngle/2)或-1
//对于非点光源y = 1/cos(spotAngle/4)或1,注意,代码中原有的对y分量的注释,有误
// z = quadratic attenuation
// w = range*range
half4unity_LightAtten[8];
// 8个光源的正前照射方向,这些方向向量基于观察空间,如果这些光源不是聚光灯光源,都为(0,0,1,0)
float4unity_SpotDirection[8];
Unity 3D技术支持工程师介绍,因为种种原因,在unity_LightAtten变量的原有代码注释中,对于y分量的描述其实是错误的。y分量的正确值应该是:聚光灯1/4张角的余弦值减去其1/2张角的余弦值。如果该差值不为0,则y为该差值的倒数,否则为1。
4. 球谐光照使用到的参数
通过以下代码定义球谐光照使用到的参数。
// 所在文件:UnityShaderVariables.cginc代码
// 所在目录:CGIncludes
// 从原文件第133行开始,至第139行结束
// 球谐光照的相关参数,详见7.7节中球谐函数的相关理论
half4unity_SHAr;
half4unity_SHAg;
half4unity_SHAb;
half4unity_SHBr;
half4unity_SHBg;
half4unity_SHBb;
half4unity_SHC;
5.和光探针相关的参数
// 所在文件:UnityShaderVariables.cginc代码
// 所在目录:CGIncludes
// 从原文件第142行开始,至第144行结束
fixed4unity_OcclusionMaskSelector;
fixed4unity_ProbesOcclusion;
CBUFFER_END
//以下变量从4.0版本开始已弃用。之所以保留它们是为了兼容现有的使用这些变量的第三方着色器
CBUFFER_START(UnityLightingOld)
half3unity_LightColor0, unity_LightColor1, unity_LightColor2, unity_LightColor3;
CBUFFER_END
4.1.4 与阴影相关的着色器常量缓冲区
UnityShadows着色器常量缓冲区
// 所在文件:UnityShaderVariables.cginc代码
// 所在目录:CGIncludes
// 从原文件第153行开始,至第162行结束
CBUFFER_START(UnityShadows)
// 用于构建层叠式阴影贴图时子视截体用到的包围球
float4unity_ShadowSplitSpheres[4];
// unity_ShadowSplitSpheres中4个包围球半径的平方
float4unity_ShadowSplitSqRadii;
float4unity_LightShadowBias;
float4_LightSplitsNear;
float4_LightSplitsFar;
// 把某个坐标点从世界空间变换到阴影贴图空间,如果使用层叠式阴影贴图
// 数组各元素就表征4个阴影贴图各自所对应的阴影贴图空间
float4x4unity_WorldToShadow[4];
half4_LightShadowData;
float4unity_ShadowFadeCenterAndType;
CBUFFER_END
4.1.5 与逐帧绘制调用相关的着色器常量缓冲区
通过以下代码定义UnityPerDraw着色器常量缓冲区。
// 所在文件:UnityShaderVariables.cginc代码
// 所在目录:CGIncludes
// 从原文件第166行开始,至第171行结束
CBUFFER_START(UnityPerDraw)
float4x4unity_ObjectToWorld;// 把顶点从局部空间变换到世界空间的变换矩阵
float4x4unity_WorldToObject;// 把顶点从世界空间变换到局部空间的变换矩阵
float4unity_LODFade;
float4unity_WorldTransformParams;
//该变量的w分量通常为1,当缩放变量值为负数时,
//常被引擎赋值为-1
CBUFFER_END
4.1.6 与雾效果相关的常量缓冲区
通过以下代码定义UnityFog着色器常量缓冲区。
// 所在文件:UnityShaderVariables.cginc代码
// 所在目录:CGIncludes
// 从原文件第236行开始,至第243行结束
CBUFFER_START(UnityFog)
fixed4unity_FogColor; // 雾的颜色
// x = density / sqrt(ln(2)), 用于雾化因子指数平方衰减
// y = density / ln(2), 用于雾化因子指数衰减
// z = -1/(end-start), 用于雾化因子线性衰减
// w = end/(end-start), 用于雾化因子线性衰减
float4unity_FogParams;
CBUFFER_END
4.1.7 与光照贴图相关的常量缓冲区
通过以下代码声明Unity_Lightmap变量。
// 所在文件:UnityShaderVariables.cginc代码
// 所在目录:CGIncludes
// 从原文件第250行开始,至第252行结束
// 声明了主光照贴图,该贴图记录了直接照明下的光照信息
UNITY_DECLARE_TEX2D_HALF(unity_Lightmap);
// 声明了间接照明所产生的光照信息,因为unity_LightmapInd和unity_Lightmap
// 搭配使用所以不用另外专门声明采样器
UNITY_DECLARE_TEX2D_NOSAMPLER_HALF(unity_LightmapInd);
2. UNITY DECLARE TEX2D NOSAMPLER宏的定义
// 所在文件:HLSLSupport.cginc代码
// 所在目录:CGIncludes
#if defined(SHADER_API_D3D11) || defined(SHADER_API_XBOXONE) ||
defined(UNITY_COMPILER_HLSLCC) || defined(SHADER_API_PSSL)
// 中间的若干行代码省略
// 本行代码在原文件第411行
#define UNITY_DECLARE_TEX2D_NOSAMPLER(tex) Texture2D tex
#else
// 中间的若干行代码省略
// 本行的代码在原文件第492行
#define UNITY_DECLARE_TEX2D_NOSAMPLER(tex) sampler2D tex
3. unity ShadowMask变量的定义
unity_ShadowMask变量的定义如下。
// 所在文件:UnityShaderVariables.cginc代码
// 所在目录:CGIncludes
// 从原文件第254行开始,至第261行结束
// 如果启用了阴影蒙版,阴影蒙版在7.5.2节中有详细解释
#if defined (SHADOWS_SHADOWMASK)
#if defined(LIGHTMAP_ON) // 如果启用了光照贴图
UNITY_DECLARE_TEX2D_NOSAMPLER(unity_ShadowMask);
#else
UNITY_DECLARE_TEX2D(unity_ShadowMask);
#endif
#endif
4.和全局照明光照贴图相关的变量
// 所在文件:UnityShaderVariables.cginc代码
// 所在目录:CGIncludes
// 从原文件第264行开始,至第271行结束
UNITY_DECLARE_TEX2D(unity_DynamicLightmap);
UNITY_DECLARE_TEX2D_NOSAMPLER(unity_DynamicDirectionality);
UNITY_DECLARE_TEX2D_NOSAMPLER(unity_DynamicNormal);
CBUFFER_START(UnityLightmaps)
// 对应于静态光照贴图变量unity_Lightmap,用于tiling和offset操作。tiling和offset
// 操作的详细信息参见4.2.5节,unity_Lightmap变量参见4.1.7节
float4unity_LightmapST;
// 对应于动态(实时)光照贴图变量unity_DynamicLightmap,用于tiling和offset操作
float4unity_DynamicLightmapST;
CBUFFER_END
5.与反射用光探针相关的着色器变量
UNITY_DECLARE_TEXCUBE(unity_SpecCube0);
UNITY_DECLARE_TEXCUBE_NOSAMPLER(unity_SpecCube1);
CBUFFER_START(UnityReflectionProbes)
// 反射用光探针的作用区域立方体是一个和世界坐标系坐标轴轴对齐的包围盒
// unity_SpecCube0_BoxMax的x、y、z分量存储了该包围盒在x、y、z轴方向上的最大边界值
// 它的值由反射用光探针的Box Size属性和Box Offset属性计算而来
float4unity_SpecCube0_BoxMax;
// unity_SpecCube0_BoxMax的x、y、z分量存储了该包围盒在x、y、z轴方向上的最小边界值
float4unity_SpecCube0_BoxMin;
// 对应于ReflectionProbe组件中的光探针位置,它由Transfrom组件的
// Position属性和Box Offset属性计算而来
float4unity_SpecCube0_ProbePosition;
// 反射用光探针使用的立方体贴图中包含高动态范围颜色,这允许它包含大于1的亮度值
// 在渲染时要将HDR值转为RGB值
half4 unity_SpecCube0_HDR;
float4unity_SpecCube1_BoxMax;
float4unity_SpecCube1_BoxMin;
float4unity_SpecCube1_ProbePosition;
half4 unity_SpecCube1_HDR;
CBUFFER_END
UNITY_DECLARE_TEXCUBE宏在HLSLSupport.cginc文件中定义,用来声明一个立方体贴图变量。而UNITY_DECLARE_TEXCUBE_NOSAMPLER宏则是一个不声明采样器变量的版本。
6.立方体贴图的声明变量、对纹理采样等相关的宏
UNITY_SAMPLE_TEXCUBE_LOD宏对立方体纹理贴图根据当前的mipmap层级进行采样。
4.2 UnityCG.cginc文件中的工具函数和宏
4.2.1 数学常数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第3行开始,至第13行结束
#ifndef UNITY_CG_INCLUDED
#define UNITY_CG_INCLUDED
#define UNITY_PI 3.14159265359f // 圆周率
#define UNITY_TWO_PI 6.28318530718f // 2倍圆周率
#define UNITY_FOUR_PI 12.56637061436f // 4倍圆周率
#define UNITY_INV_PI 0.31830988618f //圆周率的倒数
#define UNITY_INV_TWO_PI 0.15915494309f // 2倍圆周率的倒数
#define UNITY_INV_FOUR_PI 0.07957747155f // 4倍圆周率的倒数
#define UNITY_HALF_PI 1.57079632679f // 半圆周率
#define UNITY_INV_HALF_PI 0.636619772367f //半圆周率的倒数
4.2.2 与颜色空间相关的常数和工具函数
1. IsGammaSpace函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第79行开始,至第86行结束
// 用来判断当前是否启用了伽马颜色空间函数
inline boolIsGammaSpace()
{
#ifdef UNITY_COLORSPACE_GAMMA
return true;
#else
return false;
#endif
}
本函数在新版本不再使用,保留它是为了兼容旧版本及很多已经存在的着色器。
2.GammaToLinearSpaceExact函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第88行开始,至第96行结束
inline floatGammaToLinearSpaceExact (floatvalue)
{
if(value <= 0.04045F)
returnvalue / 12.92F;
else if(value < 1.0F)
returnpow((value + 0.055F)/1.055F, 2.4F);
else
returnpow(value, 2.2F);
}
GammaToLinearSpaceExact函数把一个颜色值精确地从伽马颜色空间(sRGB颜色空间)变换到线性空间(CIE-XYZ颜色空间)。
3.GammaToLinearSpace函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第98行开始,至第105行结束
inline half3GammaToLinearSpace (half3sRGB)
{
// GammaToLinearSpaceExact函数的近似模拟版本
returnsRGB * (sRGB * (sRGB * 0.305306011h + 0.682171111h) + 0.012522878h);
}
GammaToLinearSpace函数是用一个近似模拟的函数把颜色值近似地从伽马颜色空间变换到线性空间。
4.LinearToGammaSpaceExact函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第107行开始,至第117行结束
inline floatLinearToGammaSpaceExact (floatvalue)
{
if(value <= 0.0F)
return0.0F;
else if(value <= 0.0031308F)
return12.92F * value;
else if(value < 1.0F)
return1.055F * pow(value, 0.4166667F) -0.055F;
else
returnpow(value, 0.45454545F);
}
LinearToGammaSpaceExact函数把一个颜色值精确地从线性空间变换到伽马颜色空间。
5.LinearToGammaSpace函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第119行开始,至第127行结束
inline half3LinearToGammaSpace (half3linRGB)
{
linRGB = max(linRGB, half3(0.h, 0.h, 0.h));
returnmax(1.055h * pow(linRGB, 0.416666667h) -0.055h, 0.h);
}
LinearToGammaSpace函数是用一个近似模拟的函数把颜色值近似地从线性空间变换到伽马颜色空间。
4.2.3 描述顶点布局格式的结构体
1.顶点结构体appdata base
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第51行开始,至第56行结束
structappdata_base {
float4vertex : POSITION; // 世界坐标下的顶点坐标
float3normal : NORMAL; // 顶点法线
float4texcoord : TEXCOORD0; // 顶点使用的第一层纹理坐标
UNITY_VERTEX_INPUT_INSTANCE_ID // 顶点多例化的ID
};
UNITY_VERTEX_INPUT_INSTANCE_ID是定义顶点多例化ID用的一个宏。
2. 顶点结构体appdata tan
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第58行开始,至第64行结束
structappdata_tan {
float4vertex : POSITION; // 世界坐标下的顶点坐标
float4tangent : TANGENT; // 顶点切线
float3normal : NORMAL; // 顶点法线
float4texcoord : TEXCOORD0; // 顶点使用的第一层纹理坐标
UNITY_VERTEX_INPUT_INSTANCE_ID // 顶点多例化的ID
};
3.顶点结构体appdata full
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第66行开始,至第76行结束
structappdata_full {
float4vertex : POSITION; //世界坐标下的顶点坐标
float4tangent : TANGENT; // 顶点切线
float3normal : NORMAL; // 顶点法线
float4texcoord : TEXCOORD0; // 顶点使用的第一层纹理坐标
float4texcoord1 : TEXCOORD1; // 顶点使用的第二层纹理坐标
float4texcoord2 : TEXCOORD2; // 顶点使用的第三层纹理坐标
float4texcoord3 : TEXCOORD3; // 顶点使用的第四层纹理坐标
fixed4color : COLOR; // 顶点颜色
UNITY_VERTEX_INPUT_INSTANCE_ID // 顶点多例化的ID
};
4.2.4 用于进行空间变换的工具函数
1. UnityWorldToClipPos函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第130行开始,至第133行结束
inline float4UnityWorldToClipPos( in float3pos )
{
returnmul(UNITY_MATRIX_VP, float4(pos, 1.0));
}
UNITY_MATRIX_VP,即当前观察矩阵与投影矩阵的乘积。UnityWorldToClipPos函数的作用就是把世界坐标空间中的某一点pos变换到齐次裁剪空间中去。
2. UnityViewToClipPos函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第136行开始,至第139行结束
inline float4UnityViewToClipPos( in float3pos )
{
returnmul(UNITY_MATRIX_P, float4(pos, 1.0));
}
宏UNITY_MATRIX_P即当前的投影矩阵。UnityViewToClipPos函数的作用就是把观察坐标空间中的某一点pos变换到齐次裁剪空间中去。
3. 参数类型为float3的UnityObjectToViewPos函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第142行开始,至第145行结束
inline float3UnityObjectToViewPos( in float3pos )
{
returnmul(UNITY_MATRIX_V, mul(unity_ObjectToWorld, float4(pos, 1.0))).xyz;
}
UnityObjectToViewPos函数的作用就是把模型局部空间坐标系中的某一个点pos,首先变换到世界空间坐标系下,然后变换到观察空间坐标系下。
4. 参数类型为float4的UnityObjectToViewPos函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第146行开始,至第149行结束
inline float3UnityObjectToViewPos(float4pos)
{
returnUnityObjectToViewPos(pos.xyz);
}
5. UnityWorldToViewPos函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第152行开始,至第155行结束
inline float3UnityWorldToViewPos( in float3pos )
{
returnmul(UNITY_MATRIX_V, float4(pos, 1.0)).xyz;
}
UnityWorldToViewPos函数的作用是把世界坐标系下的一个点pos变换到观察坐标系下。
6. UnityObjectToWorldDir函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第158行开始,至第161行结束
inline float3UnityObjectToWorldDir( in float3dir )
{
returnnormalize(mul((float3x3)unity_ObjectToWorld, dir));
}
UnityObjectToWorldDir函数的作用是把一个方向向量从模型坐标系变换到世界坐标系下,然后对结果进行单位化。
7. UnityWorldToObjectDir函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第164行开始,至第167行结束
inline float3UnityWorldToObjectDir( in float3dir )
{
returnnormalize(mul((float3x3)unity_WorldToObject, dir));
}
UnityWorldToObjectDir函数的作用是把一个方向向量从世界坐标系变换到模型坐标系下,然后对结果进行单位化。
8. UnityObjectToWorldNormal函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第170行开始,至第178行结束
inline float3UnityObjectToWorldNormal( in float3norm )
{
#ifdef UNITY_ASSUME_UNIFORM_SCALING
returnUnityObjectToWorldDir(norm);
#else
returnnormalize(mul(norm, (float3x3)unity_WorldToObject));
#endif
}
UnityObjectToWorldNormal函数,即把某顶点的法线normal从模型坐标系下变换到世界坐标系下。
9. UnityWorldSpaceLightDir函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第181行开始,至第192行结束
inline float3UnityWorldSpaceLightDir( in float3worldPos )
{
#ifndef USING_LIGHT_MULTI_COMPILE
return_WorldSpaceLightPos0.xyz - worldPos * _WorldSpaceLightPos0.w;
#else
#ifndef USING_DIRECTIONAL_LIGHT // 如果不是平行光
return_WorldSpaceLightPos0.xyz - worldPos;
#else
return_WorldSpaceLightPos0.xyz; // 如果是平行光就直接返回
#endif
#endif
}
10. WorldSpaceLightDir函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第196行开始,至第200行结束
inline float3WorldSpaceLightDir( in float4localPos )
{
// 首先把localPos变换到世界坐标系下,然后调用UnityWorldSpaceLightDir
// 函数计算出连线方向
float3worldPos = mul(unity_ObjectToWorld, localPos).xyz;
returnUnityWorldSpaceLightDir(worldPos);
}
本函数在当前版本的Unity 3D中已经不使用,保留下来是为了兼容旧有的第三方着色器代码。
11. ObjSpaceLightDir函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第203行开始,至第215行结束
inline float3ObjSpaceLightDir( in float4v )
{
float3objSpaceLightPos = mul(unity_WorldToObject, _WorldSpaceLightPos0).xyz;
#ifndef USING_LIGHT_MULTI_COMPILE
returnobjSpaceLightPos.xyz - v.xyz * _WorldSpaceLightPos0.w;
#else
#ifndef USING_DIRECTIONAL_LIGHT
returnobjSpaceLightPos.xyz - v.xyz;
#else
returnobjSpaceLightPos.xyz;
#endif
#endif
}
12. UnityWorldSpaceViewDir函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第218行开始,至第221行结束
inline float3UnityWorldSpaceViewDir( in float3worldPos )
{
return_WorldSpaceCameraPos.xyz - worldPos;
}
UnityWorldSpaceViewDir函数在世界坐标系下计算出某位置点到摄像机位置点worldPos的连线向量。
13. WorldSpaceViewDir函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第225行开始,至第229行结束
inline float3WorldSpaceViewDir( in float4localPos )
{
float3worldPos = mul(unity_ObjectToWorld, localPos).xyz;
returnUnityWorldSpaceViewDir(worldPos);
}
本函数在当前版本的Unity 3D中已经不使用,保留下来是为了兼容旧有的第三方着色器代码。
14. ObjSpaceViewDir函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第232行开始,至第236行结束
inline float3ObjSpaceViewDir( in float4v )
{
float3objSpaceCameraPos = mul(unity_WorldToObject,
float4(_WorldSpaceCameraPos.xyz, 1)).xyz;
returnobjSpaceCameraPos - v.xyz;
}
15. TANGENT SPACE ROTATION宏
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第239行开始,至第241行结束
#define TANGENT_SPACE_ROTATION \
float3binormal = \
cross( normalize(v.normal), normalize(v.tangent.xyz) ) * v.tangent.w; \
float3x3rotation = float3x3( v.tangent.xyz, binormal, v.normal )
此宏的作用是定义一个类型为float3x3,名字为rotation的3×3矩阵。这个矩阵由顶点的法线、切线,以及与顶点的法线切线都相互垂直的副法线组成,构成了一个正交的切线空间。
4.2.5 与光照计算相关的工具函数
1. Shade4PointLights函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第246行开始,至第282行结束
// 本函数将用在ForwardBase类型的渲染通道上。参数lightPosX、lightPosY、lightPosZ的
// 4个分量依次存储了4个点光源的x坐标、y坐标、z坐标。参数lightColor0、
// lightColor1、lightColor2、lightColor3依次存储了4个点光源的颜色的RGB值。lightAttenSq的
// 4个分量依次存储了4个点光源的二次项衰减系数
float3Shade4PointLights(float4lightPosX, float4lightPosY, float4lightPosZ,
float3lightColor0, float3lightColor1, float3lightColor2, float3lightColor3,
float4lightAttenSq,float3pos, float3normal)
Shade4PointLights函数用在顶点着色器的ForwardBase渲染通道上。本函数在每一个顶点被4个点光源照亮时,利用朗伯光照模型(Lambert lighting model)计算出光照的漫反射效果。
2. ShadeVertexLightsFull函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第286行开始,至第313行结束
// 本函数用在顶点着色器中,计算出光源产生的漫反射光照效果
// float4 vertex顶点的位置坐标
// float4 normal顶点的位置坐标
// float4 lightCount参与光照计算的光源数量
// float4 spotLight光源是不是聚光灯光源
float3ShadeVertexLightsFull(float4vertex, float3normal,
intlightCount, boolspotLight)
3. ShadeVertexLights函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第315行开始,至第318行结束
float3ShadeVertexLights(float4vertex, float3normal)
{
returnShadeVertexLightsFull(vertex, normal, 4, false);
}
4. TRANSFORM TEX宏和TRANSFORM UV宏
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第435行开始,至第438行结束
#define TRANSFORM_TEX(tex,name) (tex.xy * name##_ST.xy + name##_ST.zw)
#define TRANSFORM_UV(idx) v.texcoord.xy
TRANSFORM_TEX即是用顶点中的纹理映射坐标tex与待操作纹理name的Tiling(平铺值)和Offset(偏移值)做一个运算操作。
5. 结构体v2f vertex lit
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第442行开始,至第446行结束
structv2f_vertex_lit {
float2uv : TEXCOORD0;
fixed4diff : COLOR0;
fixed4spec : COLOR1;
};
v2f_vertex_lit定义了一个顶点布局格式结构体,该结构体很简单,只用到了一层纹理,另外指定了两种颜色,用来模拟漫反射颜色和镜面反射颜色。
Vertex-Lit是实现最低保真度的光照且不支持实时阴影的渲染途径,最好使用于旧机器或受限制的移动平台上。
6. VertexLight函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第448行开始,至第455行结束
inline fixed4VertexLight( v2f_vertex_lit i, sampler2DmainTex )
{
fixed4texcol = tex2D( mainTex, i.uv );
fixed4c;
c.xyz = ( texcol.xyz * i.diff.xyz + i.spec.xyz * texcol.a );
c.w = texcol.w * i.diff.w;
returnc;
}
VertexLight是一个简单的顶点光照计算函数,其颜色计算方式就是用顶点漫反射颜色乘以纹理颜色,然后加上纹素的Alpha值与顶点镜面反射颜色,两者之和就是最终的颜色。
7. ParallaxOffset函数
// 所在文件:UnityCG.cginc
// 所在目录:CGIncludes
// 从原文件第459行开始,至第465行结束
inline float2ParallaxOffset( halfh, halfheight, half3viewDir )
{
h = h * height - height/2.0;
float3v = normalize(viewDir);
v.z += 0.42;
returnh * (v.xy / v.z);
}
ParallaxOffset函数根据当前片元对应的高度图中的高度值h,以及高度缩放系数height和切线空间中片元到摄像机的连线向量,计算到当前片元实际上要使用外观纹理的哪一点的纹理。
8. Luminance函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第468行开始,至第471行结束
inline halfLuminance(half3rgb)
{
returndot(rgb, unity_ColorSpaceLuminance.rgb);
}
Luminance函数把一个RGB颜色值转化成亮度值,当前的RGB颜色值基于伽马空间或者线性空间,得到的亮度值有不同的结果。
9.LinearRgbToLuminance函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第475行开始,至第478行结束
// 把在线性空间中的颜色RGB值转换成亮度值
halfLinearRgbToLuminance(half3linearRgb)
{
returndot(linearRgb, half3(0.2126729f,0.7151522f, 0.0721750f));
}
LinearRgbToLuminance函数是把一个在线性空间中的RGB颜色值转换成亮度值。
4.2.6 与HDR及光照贴图颜色编解码相关的工具函数
高动态范围(high dynamic range,HDR)光照是一种用来实现超过了显示器所能表现的亮度范围的渲染技术。
实际的HDR实现一般遵循以下几步:①在每个颜色通道是16位或者32位的浮点纹理或者渲染目标(float render target)上渲染当前的场景;②使用RGBM、LogLuv等编码方式来节省所需的内存和带宽;③通过降采样(down sample)计算场景亮度;④根据场景亮度值对场景做一个色调映射(tone mapping),将最终颜色值输出到一个每通道8位的RGB格式的渲染目标上。
1. UnityEncodeRGBM函数
half4UnityEncodeRGBM (half3color, floatmaxRGBM)
RGBM是一种颜色编码方式,M即shared multiplier。本函数的作用:将颜色数据编码成一个能以8位颜色分量存储的数据。
2. DecodeHDR函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第497行开始,至第451行结束
inline half3DecodeHDR(half4data, half4decodeInstructions)
3. DecodeLightmapRGBM函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第517行开始,至第529行结束
inline half3DecodeLightmapRGBM (half4data, half4decodeInstructions)
DecodeLightmapRGBM函数是把一个RGBM颜色值解码成一个每通道8位的RGB颜色。
4. DecodeLightmapDoubleLDR函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第532行开始,至第536行结束
// 解码一个用dLDR编码的光照贴图
inline half3DecodeLightmapDoubleLDR( fixed4color )
5. DecodeLightmap函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第538行开始,至第545行结束
inline half3DecodeLightmap( fixed4
color, half4decodeInstructions)
6. unity Lightmap HDR变量和DecodeLightmap函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第547行开始,至第552行结束
half4unity_Lightmap_HDR;
inline half3DecodeLightmap( fixed4color )
{
returnDecodeLightmap( color, unity_Lightmap_HDR );
}
7. DecodeDirectionalLightmap函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第571行开始,至第583行结束
inline half3DecodeDirectionalLightmap(half3color, fixed4dirTex, half3normalWorld)
{
// 半朗伯光照模型
halfhalfLambert = dot(normalWorld, dirTex.xyz -0.5) + 0.5;
// w分量用来控制该点上辐射入射度的方向性,即被dominant方向影响的程度
returncolor * halfLambert / max(1e-4h, dirTex.w);
}
光照贴图目前用得比较多而且渲染效果也不错的实现方法是定向光照贴图(directional light map),它是原始光照贴图的增强实现。它主要是通过在预处理与实时还原过程中加入场景中表面的法向量进行运算,进而增强效果。定向光照贴图技术的大致实现方式如下所示。
1)在采样点处把其所处的半球空间中的辐射入射度用某种方法进行采集并保存。
2)以某种方法存储额外的且与该辐射入射度相关的法线信息到烘焙所得的光照贴图中。
3)在运行时的实时渲染过程中,通过光照贴图对片元上的场景辐射入射度,并结合光照信息和方向信息进行还原。
8. DecodeRealtimeLightmap函数
//该函数对实时生成的光照贴图进行解码,Enlighten中间件实时生成的光照贴图
// 格式不同于一般的Unity 3D的HDR纹理
// 例如,烘焙式光照贴图、反射用光探针,还有IBL图像等
// Englithen渲染器的RGBM格式纹理是在线性颜色空间中定
// 义颜色,使用了不同的指数操作
// 要将其还原成RGB颜色需要做以下操作
inline half3DecodeRealtimeLightmap( fixed4color )
4.2.7 把高精度数据编码到低精度缓冲区的函数
EncodeFloatRGBA函数是把一个在区间[0,1]内的浮点数编码成一个float4类型的RGBA值。
DecodeFloatRGBA函数则是EncodeFloatRGBA函数的逆操作。
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第586行开始,至第599行结束
inline float4EncodeFloatRGBA( float v )
// 把一个float4类型的RBGA纹素值解码成一个float类型的浮点数
inline floatDecodeFloatRGBA( float4enc )
2. EncodeFloatRG函数和DecodeFloatRG函数
EncodeFloatRG函数与DecodeFloatRG函数的设计思想和EncodeFloatRGBA函数以及Decode FloatRGBA函数一样,只是使用了两个通道去进行编码。
3. EncodeViewNormalStereo函数和DecodeViewNormalStereo函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第619行开始,至第637行结束
inline float2EncodeViewNormalStereo( float3n )
inline float3DecodeViewNormalStereo( float4enc4 )
EncodeViewNormalStereo函数使用球极投影(stereographic projection)将观察空间中的物体的法线映射为一个2D纹理坐标值坐标。
4. EncodeDepthNormal函数和DecodeDepthNormal函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第639行开始,至第651行结束
inline float4EncodeDepthNormal( floatdepth, float3normal )
inline voidDecodeDepthNormal( float4enc, out floatdepth, out float3normal )
4.2.8 法线贴图及其编解码操作的函数
法线贴图(normal map)中存储的信息是对模型顶点法线的扰动方向向量。利用此扰动方向向量,在光照计算时对顶点原有的法线进行扰动,从而使法线方向排列有序的平滑表面产生法线方向杂乱无序从而导致表面凹凸不平的效果。因为法线贴图有如此特性,所以在实际应用中需使用“低面数模型+法线贴图”的方式实现高面数模型才能达到的精细效果。
构建切线空间的流程指定了顶点的法线,过顶点有无数条和法线垂直的切线,法线和切线叉乘求得副法线。
DXT是一种纹理压缩格式,以前称为S3TC。当前很多图形硬件已经支持这种格式,即在显存中依然保持着压缩格式,从而减少显存占用量。
要使用DXT格式压缩图像,要求图像大小至少是4×4纹素,而且图像高宽的纹素个数是2的整数次幂,如32×32、64×128等。
1. UnpackNormal函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第673行开始,至第680行结束
inline fixed3UnpackNormal(fixed4packednormal)
{
#if defined(UNITY_NO_DXT5nm)
returnpackednormal.xyz * 2-1;
#else
returnUnpackNormalmapRGorAG(packednormal);
#endif
}
2. UnpackNormalmapRGorAG函数
UnpackNormalRGorAG函数便能同时处理格进DXT5nm或者BC5格式的法线贴图,并正确地把法线扰动向量从纹素中解码出来。
3. UnpackNormalDXT5nm函数
UnpackNormalDXT5nm函数用来解码DXT5nm格式的法线贴图,其算法思想和UnpackNormalmap RGorAG函数一致,如下所示。
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第653行开始,至第659行结束
inline fixed3UnpackNormalDXT5nm(fixed4packednormal)
{
fixed3normal;
normal.xy = packednormal.wy * 2-1;
normal.z = sqrt(1- saturate( dot(normal.xy, normal.xy)));
returnnormal;
}
4.2.9 线性化深度值的工具函数
1. Linear01Depth函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第684行开始,至第687行结束
// 把从深度纹理中取得的顶点深度值z变换到观察空间中,然后映射到[0,1]区间内
// _ZBufferParams的x分量为1减去视截体远截面值与近截面值的商,_ZBufferParams的y分量为视截体
// 远截面值与近截面值的商
inline floatLinear01Depth( floatz )
{
return1.0 / (_ZBufferParams.x * z + _ZBufferParams.y);
}
2. LinearEyeDepth函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第689行开始,至第692行结束
// 把从深度纹理中取得的顶点深度值z变换到观察空间中
// _ZBufferParams的z分量为x分量除以视截体远截面值,w分量为y分量除以视截体远截面值
inline floatLinearEyeDepth( floatz )
{
return1.0 / (_ZBufferParams.z * z + _ZBufferParams.w);
}
3. 封装了操作深度纹理的工具宏
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第734行开始,至第737行结束
#define DECODE_EYEDEPTH(i) LinearEyeDepth(i)
// 取得顶点从世界空间变换到观察空间后的z值,并且取其相反数
#define COMPUTE_EYEDEPTH(o) o = -UnityObjectToViewPos( v.vertex ).z
// 取得顶点从世界空间变换到观察空间后的z值,并且取其相反数后将值映射到[0,1]范围内
#define COMPUTE_DEPTH_01-(UnityObjectToViewPos( v.vertex ).z * _ProjectionParams.w)
// 把顶点法线从世界空间变换到观察空间
#define COMPUTE_VIEW_NORMAL normalize(mul((float3x3)UNITY_MATRIX_IT_MV, v.normal))
4.2.10 合并单程立体渲染时的左右眼图像到一张纹理的函数
1. TransformStereoScreenSpaceTex函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第707行开始,至第712行结束
#if defined(UNITY_SINGLE_PASS_STEREO)
float2TransformStereoScreenSpaceTex(float2uv, floatw)
{
float4scaleOffset = unity_StereoScaleOffset[unity_StereoEyeIndex];
returnuv.xy * scaleOffset.xy + scaleOffset.zw * w;
}
TransformStereoScreenSpaceTex函数即对单程立体渲染用到的左右眼图像,放到一张可渲染纹理的左右两边时要做的缩放和偏移操作。
2. 参数类型为float2的UnityStereoTransformScreenSpaceTex函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第714行开始,至第717行结束
inline float2UnityStereoTransformScreenSpaceTex(float2uv)
{
returnTransformStereoScreenSpaceTex( saturate(uv), 1.0);
}
3. 参数类型为float4的UnityStereoTransformScreenSpaceTex函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第719行开始,至第722行结束
inline float4UnityStereoTransformScreenSpaceTex(float4uv)
{
return float4(UnityStereoTransformScreenSpaceTex(uv.xy),
UnityStereoTransformScreenSpaceTex(uv.zw));
}
4. UnityStereoClamp函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第723行开始,至第726行结束
// scaleAndOffset的x、y分量包含对纹理的缩放操作参数,z、w分量包含对纹理
// 的偏移操作参数。本函数把原始的uv坐标的u分量限定在缩放范围内
inline float2UnityStereoClamp(float2uv, float4scaleAndOffset)
5. UnityStereoScreenSpaceUVAdjustInternal函数的两个版本
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第695行开始,至第705行结束
inline float2UnityStereoScreenSpaceUVAdjustInternal(float2uv,float4scaleAndOffset)
{
returnuv.xy * scaleAndOffset.xy + scaleAndOffset.zw;
}
inline float4UnityStereoScreenSpaceUVAdjustInternal(float4uv,float4scaleAndOffset)
{
return float4(UnityStereoScreenSpaceUVAdjustInternal(uv.xy, scaleAndOffset),
UnityStereoScreenSpaceUVAdjustInternal(uv.zw, scaleAndOffset));
}
#define UnityStereoScreenSpaceUVAdjust(x, y) \ UnityStereoScreenSpaceUVAdjustInternal(x, y)
UnityStereoScreen SpaceUVAdjust宏封装了计算缩放和偏移的UnityStereoScreenSpaceUVAdjustInternal函数。
4.2.11 用来实现图像效果的工具函数和预定义结构体
1. 结构体appdata_img
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第742行开始,至第747行结束
structappdata_img
{
float4vertex : POSITION; // 顶点的齐次化位置坐标
half2texcoord : TEXCOORD0; // 顶点用到的第一层纹理映射坐标
UNITY_VERTEX_INPUT_INSTANCE_ID // 硬件instance id值
};
在实现图像效果时,顶点着色器会用到一些简单的顶点描述结构体。appdata_img即定义了这样的结构体。
2. 结构体v2f_img
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第749行开始,至第755行结束
structv2f_img
{
float4pos : SV_Position; // 要传递给片元着色器的顶点坐标,已变换到裁剪空间中
half2uv : TEXCOORD0; // 用到的第一层纹理映射坐标
UNITY_VERTEX_INPUT_INSTANCE_ID
// 立体渲染时的左右眼索引。此宏在UnityInstancing.cginc文件中定义
UNITY_VERTEX_OUTPUT_STEREO
};
在实现图像效果时,顶点经过顶点处理阶段后,顶点着色器会返回一个结构体传递给片元着色器使用。v2f_img即定义了这样的一个结构体。
3. MultiplyUV函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第757行开始,至第761行结束
float2MultiplyUV (float4x4mat, float2inUV) {
float4temp = float4(inUV.x, inUV.y, 0, 0);
temp = mul(mat, temp);
returntemp.xy;
}
MultiplyUV函数的功能就是把纹理坐标向量从二维填充到四维,右乘变换矩阵得到结果向量后,再取结果向量的前两个分量返回。
4. 实现图像效果时的顶点着色器入口函数vert_img
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第763行开始,至第773行结束
v2f_img vert_img( appdata_img v )
{
v2f_img o;
UNITY_INITIALIZE_OUTPUT(v2f_img, o);
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
o.pos = UnityObjectToClipPos (v.vertex);
o.uv = v.texcoord;
returno;
}
4.2.12 计算屏幕坐标的工具函数
1. ComputeNonStereoScreenPos函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第776行开始,至第783行结束
#define V2F_SCREEN_TYPE float4
// float4 pos是在裁剪空间中的一个齐次坐标值
inline float4ComputeNonStereoScreenPos(float4pos)
{
float4o = pos * 0.5f;
o.xy = float2(o.x, o.y*_ProjectionParams.x) + o.w;
o.zw = pos.zw;
returno;
}
传入给ComputeNonStereoScreenPos函数的参数pos是顶点从其局部空间变换到裁剪空间后得到的齐次坐标值。顾名思义,当不使用立体渲染时调用此函数才有效。传递给本函数的参数应是一个在裁剪空间中的齐次坐标,一般调用UnityObjectToClipPos函数计算获得。
ComputeNonStereoScreenPos函数的函数名中虽然带有ScreenPos,但调用完该函数后,并不会得到某个顶点在经过一系列变换操作后,对应生成的片元在屏幕坐标系下的坐标值。如果要求得在屏幕坐标系下的值,应该使用如下所示代码求得。
float4 sPos // 经ComputeNonStereoScreenPos函数计算得到的值
float2 vPP; // 视口坐标(view port position)
float2 vPWH; // 视口的高和宽(view Port Width & Height)
vPP = sPos.xy / sPos.w * vPWH;
在上面的代码中,首先把sPos变量的x 、y分量各自除以自身的w分量,该步实质上就是光栅化阶段中的透视除法。
Unity 3D封装了一个真正把视口坐标转换成屏幕像素坐标的UnityPixelSnap函数。
4. ComputeScreenPos函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第785行开始,至第791行结束
inline float4ComputeScreenPos(float4pos)
{
float4o = ComputeNonStereoScreenPos(pos);
#if defined(UNITY_SINGLE_PASS_STEREO)
o.xy = TransformStereoScreenSpaceTex(o.xy, pos.w);
#endif
returno;
}
5. ComputeGrabScreenPos函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第793行开始,至第806行结束
// 参数pos是一个基于裁剪空间的齐次坐标值
inline float4ComputeGrabScreenPos(float4pos) {
#if UNITY_UV_STARTS_AT_TOP
floatscale = -1.0;
#else
floatscale = 1.0;
#endif
float4o = pos * 0.5f; //第1步
o.xy = float2(o.x, o.y*scale) + o.w;// 第2步
#ifdef UNITY_SINGLE_PASS_STEREO
o.xy = TransformStereoScreenSpaceTex(o.xy, pos.w);
#endif
o.zw = pos.zw;
returno;
}
该函数传递进来在裁剪空间中某点的齐次坐标值,返回该点在目标纹理中的纹理贴图坐标。
6. UnityPixelSnap函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第809行开始,至第815行结束
inline float4UnityPixelSnap (float4pos)
{
float2hpc = _ScreenParams.xy * 0.5f;
float2pixelPos = round((pos.xy / pos.w) * hpc);
pos.xy = pixelPos / hpc * pos.w;
returnpos;
}
UnityPixelSnap函数的功能是把一个视口坐标转换成屏幕像素坐标。
7. 两个版本的TransformViewToProjection函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第817行开始,至第823行结束
inline float2TransformViewToProjection (float2v) {
returnmul((float2x2)UNITY_MATRIX_P, v);
}
inline float3TransformViewToProjection (float3v) {
returnmul((float3x3)UNITY_MATRIX_P, v);
}
4.2.13 与阴影处理相关的工具函数
1. UnityEncodeCubeShadowDepth函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第817行开始,至第823行结束
float4UnityEncodeCubeShadowDepth(floatz)
{
#ifdef UNITY_USE_RGBA_FOR_POINT_SHADOWS
returnEncodeFloatRGBA( min(z, 0.999));
#else
returnz;
#endif
}
UnityEncodeCubeShadowDepth函数的功能是把一个float类型的阴影深度值编码进一个float4类型的RGBA数值中。
2. UnityDecodeCubeShadowDepth函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第827行开始,至第834行结束
// 把一个从立方体纹理贴图中获取到的颜色值转化为一个深度值
floatUnityDecodeCubeShadowDepth(float4vals)
{
// 如果从一个点光源生成的阴影中获取到,那么深度值就会被编
// 码放到R、G、B、A这4个颜色通道中,若再用就要解码出来
#ifdef UNITY_USE_RGBA_FOR_POINT_SHADOWS
returnDecodeFloatRGBA (vals);
#else
returnvals.r;
#endif
}
UnityDecodeCubeShadowDepth函数的功能是把一个float4类型的阴影深度值解码到一个float类型的浮点数中。
3. UnityClipSpaceShadowCasterPos函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第846行开始,至第870行结束
// vertex,物体顶点在模型空间中的坐标值
// normal,物体法线在模型空间中的坐标值
float4UnityClipSpaceShadowCasterPos(float4vertex, float3normal)
{
// 把顶点从模型空间转换到世界空间
float4wPos = mul(unity_ObjectToWorld, vertex);
if(unity_LightShadowBias.z != 0.0)
{
// 把法线normal从模型空间转换到世界空间
float3wNormal = UnityObjectToWorldNormal(normal);
//UnityWorldSpaceLightDir函数计算世界坐标系下光源位置点_WorldSpaceLightPos0与
//世界坐标系下点wPos的连线的方向向量_WorldSpaceLightPos0的定义见4.1.3节
float3wLight = normalize(UnityWorldSpaceLightDir(wPos.xyz));
// 计算光线与法线的夹角的余弦值
floatshadowCos = dot(wNormal, wLight);
floatshadowSine = sqrt(1-shadowCos*shadowCos);
// unity_LightShadowBias各分量值详见4.1.4节
floatnormalBias = unity_LightShadowBias.z * shadowSine;
wPos.xyz -= wNormal * normalBias; // 沿着法线进行偏移
}
returnmul(UNITY_MATRIX_VP, wPos); //把进行了偏移之后的值变换到裁剪空间
}
UnityClipSpaceShadowCasterPos函数就是根据法线与光线的夹角的正弦值得到需要的偏移值,然后沿着法线做偏移。
4. UnityApplyLinearShadowBias函数
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第878行开始,至第891行结束
float4UnityApplyLinearShadowBias(float4clipPos)
{
// UNITY_REVERSED_Z宏的含义参见4.2.14节
#if defined(UNITY_REVERSED_Z)
// 对UnityClipSpaceShadowCasterPos函数计算出来的坐标做一个增加,并且要保证不能越过远近截面值
clipPos.z += max(-1, min(unity_LightShadowBias.x / clipPos.w, 0));
floatclamped = min(clipPos.z,clipPos.w*UNITY_NEAR_CLIP_VALUE);
#else
clipPos.z += saturate(unity_LightShadowBias.x/clipPos.w);
floatclamped = max(clipPos.z, clipPos.w*UNITY_NEAR_CLIP_VALUE);
#endif
clipPos.z = lerp(clipPos.z, clamped, unity_LightShadowBias.y);
// 根据第一次增加后的z值和z的极值,进行线性插值
returnclipPos;
}
UnityClipSpaceShadowCasterPos函数的功能是将阴影投射者(shadow caster)的坐标沿着其法线做了一定偏移之后再变换至裁剪空间。UnityApplyLinearShadowBias函数的功能是将调用UnityClipSpaceShadowCasterPos函数得到的裁剪空间坐标的z值再做一定的增加。
5. V2F SHADOW CASTER NOPOS宏和SHADOW CASTER FRAGMENT宏
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第894行开始,至第913行结束
#ifdef SHADOWS_CUBE
// 用来存储在世界坐标系下当前顶点到光源位置的连线向量
#define V2F_SHADOW_CASTER_NOPOS float3 vec : TEXCOORD0;
#define TRANSFER_SHADOW_CASTER_NOPOS_LEGACY(o,opos) \
o.vec = mul(unity_ObjectToWorld, v.vertex).xyz - \_LightPositionRange.xyz;\
opos = UnityObjectToClipPos(v.vertex);
// x、y、z分量为光源的位置,w分量为光源的照射范围的倒数,TRANSFER_SHADOW_CASTER_NOPOS
// 的功能是计算在世界坐标系下当前顶点到光源位置的连线向量,同时把顶点位置变换到裁剪空间。
// _LightPositionRange着色器变量参见4.1.3节
#define TRANSFER_SHADOW_CASTER_NOPOS(o,opos) \
o.vec = mul(unity_ObjectToWorld, v.vertex).xyz - \_LightPositionRange.xyz;\
opos = UnityObjectToClipPos(v.vertex);
// 把一个float类型的阴影深度值编码到一个float4类型中并返回
#define SHADOW_CASTER_FRAGMENT(i) \
return UnityEncodeCubeShadowDepth( \
(length(i.vec) + unity_LightShadowBias.x) * _LightPositionRange.w);
#else
// 渲染由平行光或者聚光灯光源产生的阴影
#define V2F_SHADOW_CASTER_NOPOS
#define V2F_SHADOW_CASTER_NOPOS_IS_EMPTY
#define TRANSFER_SHADOW_CASTER_NOPOS_LEGACY(o,opos) \
opos = UnityObjectToClipPos(v.vertex.xyz); \
opos = UnityApplyLinearShadowBias(opos);
#define TRANSFER_SHADOW_CASTER_NOPOS(o,opos) \
// UnityClipSpaceShadowCasterPos函数参见4.2.13节
// UnityApplyLinearShadowBias参见4.2.13节
opos = UnityClipSpaceShadowCasterPos(v.vertex, v.normal); \
opos = UnityApplyLinearShadowBias(opos);
#define SHADOW_CASTER_FRAGMENT(i) return 0;
#endif
4.2.14 与雾效果相关的工具函数和宏
1. UNITY_Z_0_FAR_FROM_CLIPSPACE宏
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第945行开始,至第966行结束
#if defined(UNITY_PASS_PREPASSBASE) || defined(UNITY_PASS_DEFERRED) ||
defined(UNITY_PASS_SHADOWCASTER)
#undef FOG_LINEAR
#undef FOG_EXP
#undef FOG_EXP2
#endif
#if defined(UNITY_REVERSED_Z)
#if UNITY_REVERSED_Z == 1
// 不经Z轴反向操作的OpenGL平台上的裁剪空间坐标
// 当裁剪空间坐标未经透视除法时,坐标的z值的取值范围是[0,far];UNITY_RESVERSED_Z
// 启用了之后,未经透视除法的坐标的z值的取值范围是[near,0];离当前摄像机越远其距离值
// 越小,不符合雾化因子的计算方式,所以要将其重新映射到[0,far]为0.1。
// 现令n = _ProjectionParams.y = 0.1,f = _ProjectionParams.z = 100,coord的
// 值为0,计算后得到值为100;令coord的值为0.1,计算后得到值为0;令coord值为0.04,
// 计算后得到值为60,即重新把[0.1,0]的数值映射回[0,100]
#define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) \
max(((1.0-(coord)/
_ProjectionParams.y)*_ProjectionParams.z),0)
#else
// 经过Z轴反向操作的OpenGL平台上的裁剪空间坐标,z值的取值范围是[near, -far]因为在
// 实践操作中,near值通常都会取很小,如0.1等,[near,-far]近似等于[0,-far],所以为了
// 提升性能,直接对坐标值取反即可
#define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) max(-(coord), 0)
#endif
#elif UNITY_UV_STARTS_AT_TOP
// 不经Z轴反向操作的OpenGL平台上的裁剪空间坐标
// 这时坐标的z值的取值范围是[0, far],离当前视点越远距离值越大,所以不用做任何重计算操作
#define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) (coord)
#else
// 不经Z轴反向操作的OpenGL平台上的裁剪空间坐标
// 这时坐标的z值的取值范围是[-near, far],离当前视点越远距离值越大,虽然理论上要处理掉深度值
// 为负值的问题,但因为是离当前摄像机越远距离值越大,雾化因子依然能保持和离当前摄像机距离值的
// 增函数关系,所以不用做任何重计算操作
#define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) (coord)
#endif
在计算雾化因子时,需要取得当前片元和摄像机的距离的绝对值。而这个距离的绝对值要通过片元在裁剪空间中的z值计算得到。在不同平台下,裁剪空间的z取值范围有所不同。所以UNITY_Z_0_FAR_FROM_CLIPSPACE宏就是把各个平台的差异化给处理掉。
2. UNITY_REVERSED_Z宏和UNITY_NEAR_CLIP_VALUE宏
// 所在文件:HLSLSupport.cginc代码
// 所在目录:CGIncludes
// 从原文件第626行开始,至第637行结束
// D3D11、PlayStation、XboxOne、Metal、Vulkan、Switch平台上逆转裁剪空间的near-far取值即
// near值为1,far为0
#if defined(SHADER_API_D3D11) || defined(SHADER_API_PSSL) ||
defined(SHADER_API_XBOXONE) || defined(SHADER_API_METAL) ||
defined(SHADER_API_VULKAN) || defined(SHADER_API_SWITCH)
#define UNITY_REVERSED_Z 1
#endif
#if defined(UNITY_REVERSED_Z)
#define UNITY_NEAR_CLIP_VALUE (1.0) // near值为1,far值为0
#elif defined(SHADER_API_D3D9) || defined(SHADER_API_WIIU) ||
defined(SHADER_API_D3D11_9X)
// D3D9和D311的9功能级别依然保持near值为0,far值为1
#define UNITY_NEAR_CLIP_VALUE (0.0)
#else // 其他平台,如OpenGL(ES)平台保持near值为-1,far值为1
#define UNITY_NEAR_CLIP_VALUE (-1.0)
#endif
3. 不同雾化因子计算方式UNITY_CALC_FOG_FACTOR_RAW宏
在Other Settings选项中选中Fog复选框后,有Color、Mode、Density这3个选项:Color是雾的颜色,Density是雾的浓度。Mode下拉列表中有Linear、Exponential、Exponential Squared这3个选项,依次对应于启用FOG_LINEAR、FOG_EXP、FOG_EXP2这3个宏。
FOG_LINEAR // 雾化因子线性化衰减
FOG_EXP // 雾化因子指数衰减
FOG_EXP2 // 雾化因子指数衰减
实现方式参考:
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第967行开始,至第979行结束
4. UNITY CALC FOG FACTOR宏和UNITY FOG COORDS PACKED宏
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第981行开始,至第982行结束
#define UNITY_CALC_FOG_FACTOR(coord) \
UNITY_CALC_FOG_FACTOR_RAW(UNITY_Z_0_FAR_FROM_CLIPSPACE(coord))
#define UNITY_FOG_COORDS_PACKED(idx,vectype)vectype fogCoord:TEXCOORD##idx;
UNITY_CALC_FOG_FACTOR宏的参数coord是未经透视除法的裁剪空间中的坐标值z分量,封装了UNITY_CALC_FOG_FACTOR_RAW的实现。参数coord经过宏UNITY_Z_0_FAR_FROM_CLIPSPACE先处理,以解决不同平台下对应的z轴倒置的问题。
UNITY_FOG_COORDS_PACKED宏则是利用顶点格式声明中的纹理坐标语义,借用一个纹理坐标寄存器把雾化因子声明在一个顶点格式结构体中。如果使用UNITY_FOG_COORDS_PACKED宏,则在顶点着色器中计算雾化效果。
5. 不同平台和不同雾化因子计算方式下的UNITY TRANSFER FOG宏
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第983行开始,至第1000行结束
#if defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2)
#define UNITY_FOG_COORDS(idx) UNITY_FOG_COORDS_PACKED(idx, float1)
#if (SHADER_TARGET < 30) || defined(SHADER_API_MOBILE)
// 如果使用移动平台或者使用shade model 2.0的平台,则在顶点中计算雾化效果
#define UNITY_TRANSFER_FOG(o,outpos) \
UNITY_CALC_FOG_FACTOR((outpos).z);
o.fogCoord.x = unityFogFactor
#else
// 如果是使用shader model 3.0的平台,或者使用PC以及一些游戏主机平台,就在顶点着色
// 器中计算每个顶点离当前摄像机的距离。在片元着色器中计算雾化因子
#define UNITY_TRANSFER_FOG(o,outpos) o.fogCoord.x = (outpos).z
#endif
#else
#define UNITY_FOG_COORDS(idx)
#define UNITY_TRANSFER_FOG(o,outpos)
#endif
6. UNITY_FOG_LERP_COLOR宏
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第1003行开始,至第1003行结束
#define UNITY_FOG_LERP_COLOR(col,fogCol,fogFac) \
col.rgb = lerp((fogCol).rgb, (col).rgb, saturate(fogFac))
UNITY_FOG_LERP_COLOR宏的功能是利用雾的颜色和当前像素的颜色,根据雾化因子进行线性插值运算,得到最终的雾化效果颜色。
7. UNITY_APPLY_FOG_COLOR宏
// 所在文件:UnityCG.cginc代码
// 所在目录:CGIncludes
// 从原文件第1004行开始,至第1019行结束
#if defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2)
#if (SHADER_TARGET < 30) || defined(SHADER_API_MOBILE)
//在移动平台或者使用shader model 2.0的平台中,因为雾化因子已经
//在顶点着色器中计算过了,所以直接在片元着色器中插值以计算雾化效果颜色
#define UNITY_APPLY_FOG_COLOR(coord,col,fogCol) \
UNITY_FOG_LERP_COLOR(col,fogCol,(coord).x)
#else
// 如果是PC或者游戏主机平台,或者是使用shader model 3.0的平台
// 将在片元着色器中计算雾化因子,然后在片元着色器中通过插值计算雾化效果的颜色
#define UNITY_APPLY_FOG_COLOR(coord,col,fogCol)
UNITY_CALC_FOG_FACTOR((coord).x);\
UNITY_FOG_LERP_COLOR(col,fogCol,unityFogFactor)
#endif
#else
#define UNITY_APPLY_FOG_COLOR(coord,col,fogCol)
#endif
#ifdef UNITY_PASS_FORWARDADD
#define UNITY_APPLY_FOG(coord,col)
UNITY_APPLY_FOG_COLOR(coord,col,fixed4(0,0,0,0))
#else
#define UNITY_APPLY_FOG(coord,col)
UNITY_APPLY_FOG_COLOR(coord,col,unity_FogColor)
#endif
UNITY_APPLY_FOG_COLOR宏定义在不同平台上的最终雾化效果的颜色计算方法。