一、数学常数

unity3D内置着色器定义了一系列的数学常数,如下:

从第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      //半圆周率的倒数

二、与颜色空间相关的常数和工具函数

2.1 IsGammaSpace函数的定义如下:

//用来判断当前是否启用了伽马颜色空间函数
inline bool IsGammaSpace()
{#ifdef UNITY_COLORSPACE_GAMMAreturn true;#elsereturn false;#endif
}

上述代码段中的IsGammaSpace函数根据宏UNITY_COLORSPACE_GAMMA是否被启用了,判断当前是否启用了伽马颜色空间。

2.2 GammaToLinearSpaceExact函数

从88行开始,96行结束:

inline float GammaToLinearSpaceExact (float value)
{if (value <= 0.04045F)return value / 12.92F;else if (value < 1.0F)return pow((value + 0.055F)/1.055F, 2.4F);elsereturn pow(value, 2.2F);
}

上述代码段中的GammaToLinearSpaceExact函数把一个颜色值精确地从伽马颜色空间(sRGB颜色空间)变化到线性空间(CIE-XYZ颜色空间)。

2.3 GameToLinearSpace函数

从98行开始,105行结束:

inline half3 GammaToLinearSpace (half3 sRGB)
{//GammaToLinearSpaceExact函数的近似模拟版本return sRGB * (sRGB * (sRGB * 0.305306011h + 0.682171111h) + 0.012522878h);// Precise version, useful for debugging.//return half3(GammaToLinearSpaceExact(sRGB.r), GammaToLinearSpaceExact(sRGB.g), GammaToLinearSpaceExact(sRGB.b));
}

上述代码段中的GameToLinearSpace函数是用一个近似模拟的函数把颜色值近似地从伽马空间变换到线性空间。

2.4  LinearToGammaSpaceExact函数

从107行开始,117行结束:

inline float LinearToGammaSpaceExact (float value)
{if (value <= 0.0F)return 0.0F;else if (value <= 0.0031308F)return 12.92F * value;else if (value < 1.0F)return 1.055F * pow(value, 0.4166667F) - 0.055F;elsereturn pow(value, 0.45454545F);
}

上述函数把一个颜色值精确地从线性空间变换到伽马颜色空间。

2.5 LinearToGammaSpace函数

从119行开始,第127行结束:

inline half3 LinearToGammaSpace (half3 linRGB)
{linRGB = max(linRGB, half3(0.h, 0.h, 0.h));// An almost-perfect approximation from http://chilliant.blogspot.com.au/2012/08/srgb-approximations-for-hlsl.html?m=1return max(1.055h * pow(linRGB, 0.416666667h) - 0.055h, 0.h);// Exact version, useful for debugging.//return half3(LinearToGammaSpaceExact(linRGB.r), LinearToGammaSpaceExact(linRGB.g), LinearToGammaSpaceExact(linRGB.b));
}

这个函数用一个近似模拟的函数把颜色值近似地从线性空间变换到伽马颜色空间。

三、描述顶点布局格式的结构体

因为在不同场合中渲染引擎需要顶点携带的信息是不同的,如果只用一个把所有顶点信息都添加在内的结构体去描述顶点,在很多场合会造成数据的冗余。因此unity预定了若干用于描述顶点结构布局的结构体版本,方便在不同场合下使用。

3.1 顶点结构体 appdata_base

从51行开始,56行结束:

struct appdata_base {float4 vertex : POSITION;    //世界坐标下的顶点坐标float3 normal : NORMAL;      //顶点法线float4 texcoord : TEXCOORD0;    //顶点使用的第一层纹理坐标UNITY_VERTEX_INPUT_INSTANCE_ID    //顶点多例化的ID
};

UNITY_VERTEX_INPUT_INSTANCE_ID是定义顶点多例化ID用的一个宏,后面会讲到。

3.2 顶点结构体 appdata_tan

从第58行开始,64行结束:

struct appdata_tan {float4 vertex : POSITION;   //世界坐标下的顶点坐标float4 tangent : TANGENT;;//顶点切线float3 normal : NORMAL;//顶点法线float4 texcoord : TEXCOORD0; //顶点使用的第一层纹理坐标UNITY_VERTEX_INPUT_INSTANCE_ID  //顶点多例化的ID
};

相对于appdata_base结构体,多了一个顶点的切线信息,在使用法线贴图技术时需要利用顶点的切线。

3.2 顶点结构体 appdata_full

从第66行开始,76行结束:

struct appdata_full {float4 vertex : POSITION;//世界坐标下的顶点坐标float4 tangent : TANGENT;//顶点切线float3 normal : NORMAL;//顶点法线float4 texcoord : TEXCOORD0;//顶点使用的第一层纹理坐标float4 texcoord1 : TEXCOORD1;//顶点使用的第二层纹理坐标float4 texcoord2 : TEXCOORD2;//顶点使用的第三层纹理坐标float4 texcoord3 : TEXCOORD3;//顶点使用的第四层纹理坐标fixed4 color : COLOR;//顶点颜色UNITY_VERTEX_INPUT_INSTANCE_ID//顶点多例化的ID
};

上述代码段中appdata_full定义了最全的顶点信息结构体,该结构体有四层纹理坐标和顶点颜色。在做地形渲染时,通常会用到非常多的纹理以产生复杂多变的地面效果。

四、用于进行空间变换的工具函数

实际开发中经常会遇到把某一个位置坐标或者方向向量从一个空间坐标系下变换到另一个空间坐标系的要求。

4.1 UnityWorldToClipPos函数

// Tranforms position from world to homogenous space
inline float4 UnityWorldToClipPos( in float3 pos )
{return mul(UNITY_MATRIX_VP, float4(pos, 1.0));
}

宏UNITY_MATRIX_VP是当前观察矩阵与投影矩阵的乘积,该函数作用就是把世界坐标空间中某一点pos变换到齐次裁剪空间中去。

4.2 UnityViewToClipPos函数

// Tranforms position from view to homogenous space
inline float4 UnityViewToClipPos( in float3 pos )
{return mul(UNITY_MATRIX_P, float4(pos, 1.0));
}

宏是当前的投影矩阵,该函数的作用就是把观察坐标空间中某一点pos变换到齐次裁剪空间中去。

4.3 参数类型为float3的UnityObjectToViewPos函数

// Tranforms position from object to camera space
inline float3 UnityObjectToViewPos( in float3 pos )
{return mul(UNITY_MATRIX_V, mul(unity_ObjectToWorld, float4(pos, 1.0))).xyz;
}

宏是当前的观察矩阵,该函数作用就是把模型局部空间坐标系中某一个点pos,首先变换到世界空间坐标系下,然后变换到观察空间坐标系下。

4.4 参数类型为float4的UnityObjectToViewPos函数

inline float3 UnityObjectToViewPos(float4 pos) // overload for float4; avoids "implicit truncation" warning for existing shaders
{return UnityObjectToViewPos(pos.xyz);
}

定义这个重载版本是为了当传递float4类型参数到UnityObjectToViewPos(int float3 pos)函数时,避免出现隐式截断问题。

4.5 UnityWorldToViewPos函数

// Tranforms position from world to camera space
inline float3 UnityWorldToViewPos( in float3 pos )
{return mul(UNITY_MATRIX_V, float4(pos, 1.0)).xyz;
}

该函数作用是把世界坐标系下的一个点pos变换到观察空间坐标系下。

4.6 UnityObjectToWorldDir函数

该函数的作用是把一个方向向量从模型坐标系变换到世界坐标系下,然后对结果进行单位化。

// Transforms direction from object to world space
inline float3 UnityObjectToWorldDir( in float3 dir )
{return normalize(mul((float3x3)unity_ObjectToWorld, dir));
}

4.7  UnityWorldToObjectDir函数

该函数的作用是把一个方向向量从世界坐标系变换到模型坐标系下,然后对结果进行单位化。

// Transforms direction from world to object space
inline float3 UnityWorldToObjectDir( in float3 dir )
{return normalize(mul((float3x3)unity_WorldToObject, dir));
}

4.8 UnityObjectToWorldNormal函数

// Transforms normal from object to world space
inline float3 UnityObjectToWorldNormal( in float3 norm )
{
#ifdef UNITY_ASSUME_UNIFORM_SCALINGreturn UnityObjectToWorldDir(norm);
#else// mul(IT_M, norm) => mul(norm, I_M) => {dot(norm, I_M.col0), dot(norm, I_M.col1), dot(norm, I_M.col2)}return normalize(mul(norm, (float3x3)unity_WorldToObject));
#endif
}

该函数中,把一个顶点从模型坐标系变换到世界坐标系上的变换矩阵为unity_ObjectToWorld,则把该顶点的法线从模型坐标系变换到世界坐标系的矩阵应是unity_ObjectToWorld的逆转置矩阵,如上写法即可。

4.9 UnityWorldSpaceLightDir函数

// Computes world space light direction, from world space position
inline float3 UnityWorldSpaceLightDir( in float3 worldPos )
{#ifndef USING_LIGHT_MULTI_COMPILEreturn _WorldSpaceLightPos0.xyz - worldPos * _WorldSpaceLightPos0.w;#else#ifndef USING_DIRECTIONAL_LIGHT    //如果不是平行光return _WorldSpaceLightPos0.xyz - worldPos;#elsereturn _WorldSpaceLightPos0.xyz;   //如果是平行光直接返回#endif#endif
}

该函数中,输入参数worldPos是一个世界坐标系下的坐标,该函数用于计算这个坐标到同样在世界坐标系下并且内置在引擎中的光源位置点_WorldSpaceLightPos0的连线的方向向量。

4.10 WorldSpaceLightDir函数

// Computes world space light direction, from object space position
// *Legacy* Please use UnityWorldSpaceLightDir instead
inline float3 WorldSpaceLightDir( in float4 localPos )
{//首先把localPos变换到世界坐标系下,然后调用UnityWorldSpaceLightDir函数计算出连线方向float3 worldPos = mul(unity_ObjectToWorld, localPos).xyz;return UnityWorldSpaceLightDir(worldPos);
}

该函数首先把传递进来的局部坐标系下某个坐标localPos变换到世界坐标系下,然后在进行上个函数的操作,本函数在当前版本的unity3D中已经不使用,保留下来是为了兼容旧有的第三方着色器代码。

4.11 ObjSpaceLightDir函数

// Computes object space light direction
inline float3 ObjSpaceLightDir( in float4 v )
{float3 objSpaceLightPos = mul(unity_WorldToObject, _WorldSpaceLightPos0).xyz;#ifndef USING_LIGHT_MULTI_COMPILEreturn objSpaceLightPos.xyz - v.xyz * _WorldSpaceLightPos0.w;#else#ifndef USING_DIRECTIONAL_LIGHTreturn objSpaceLightPos.xyz - v.xyz;#elsereturn objSpaceLightPos.xyz;#endif#endif
}

上述代码段中的ObjSpaceLightDir函数和WorldSpaceLightDir函数类似,只是把_WorldSpaceLightPos0位置变换到模型坐标系下,计算出光源位置点和v的连线的方向向量。

4.12 UnityWorldSpaceViewDir函数

// Computes world space view direction, from object space position
inline float3 UnityWorldSpaceViewDir( in float3 worldPos )
{return _WorldSpaceCameraPos.xyz - worldPos;
}

该函数在世界坐标系下计算出某位置点到摄像机位置点worldPos的连线向量。

4.13 WorldSpaceViewDir函数

// Computes world space view direction, from object space position
// *Legacy* Please use UnityWorldSpaceViewDir instead
inline float3 WorldSpaceViewDir( in float4 localPos )
{float3 worldPos = mul(unity_ObjectToWorld, localPos).xyz;return UnityWorldSpaceViewDir(worldPos);
}

传递进来的参数localPos是一个基于模型坐标系下的位置值,需要先把localPos变换到世界坐标系下,再转调用UnityWorldSpaceViewDir函数得到连线向量,在当前版本unity3D已经不使用,保留下来只是为了兼容旧有的第三方着色器代码。

4.14 ObjectSpaceViewDir函数

// Computes object space view direction
inline float3 ObjSpaceViewDir( in float4 v )
{float3 objSpaceCameraPos = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos.xyz, 1)).xyz;return objSpaceCameraPos - v.xyz;
}

把_WorldSpaceCameraPos位置变换到当前的模型坐标系下,计算出摄像机位置点objSpaceCameraPos和v的连线的方向向量。

4.15 TANGENT_SPACE_ROTATION宏的定义如下:

// Declares 3x3 matrix 'rotation', filled with tangent space basis
#define TANGENT_SPACE_ROTATION \float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz) ) * v.tangent.w; \float3x3 rotation = float3x3( v.tangent.xyz, binormal, v.normal )

定义一个宏TANGENT_SPACE_ROTATION,此宏的作用是定义一个类型为float3X3,名字为rotation的3X3矩阵。这个矩阵由顶点的法线、切线、以及与顶点的法线切线都相互垂直的副法线组成,构成了一个正交的切线空间。

五、与光照计算相关的工具函数

5.1 Shade4PointLights函数

// Used in ForwardBase pass: Calculates diffuse lighting from 4 point lights, with data packed in a special way.
//本函数将用在ForwardBase类型的渲染通道上,参数lightPosX、lightPosY、lightPosZ的4个分量依次存储了4个点光源的x坐标、
//y坐标、z坐标。参数lightColor0、lightColor1、lightColor2、lightColor3依次存储了4个点光源的颜色的RGB值。
//lightAttenSq的4个分量依次存储了4个点光源的二次项衰减系数。
float3 Shade4PointLights (float4 lightPosX, float4 lightPosY, float4 lightPosZ,float3 lightColor0, float3 lightColor1, float3 lightColor2, float3 lightColor3,float4 lightAttenSq,float3 pos, float3 normal)
{// to light vectors//一次性计算顶点到每一个光源之间的x坐标差、y坐标差、z坐标差float4 toLightX = lightPosX - pos.x;float4 toLightY = lightPosY - pos.y;float4 toLightZ = lightPosZ - pos.z;// squared lengths//一次性计算顶点到每一个光源的距离的平方float4 lengthSq = 0;lengthSq += toLightX * toLightX;lengthSq += toLightY * toLightY;lengthSq += toLightZ * toLightZ;// don't produce NaNs if some vertex position overlaps with the light//如果顶点离光源太近了,就微调一个很小的数作为他们的距离lengthSq = max(lengthSq, 0.000001);// Ndot//计算顶点到4个光源连线的向量,以及顶点法线normal的夹角的余弦值,即顶点到4个光源在法线的投影float4 ndotl = 0;ndotl += toLightX * normal.x;ndotl += toLightY * normal.y;ndotl += toLightZ * normal.z;// correct NdotL//因为由toLightX、toLightY、toLightZ组成的顶点到4个光源连线的向量是没有经过单位化的,//所以ndot1变量中的每一个分量必须除以lengthSq变量中的每一个分量,即顶点到每一个光源的距离的平方。float4 corr = rsqrt(lengthSq);ndotl = max (float4(0,0,0,0), ndotl * corr);// attenuation//计算出从光源到顶点位置的光的衰减值float4 atten = 1.0 / (1.0 + lengthSq * lightAttenSq);//衰减值在乘以夹角余弦float4 diff = ndotl * atten;// final color//计算出最终的颜色float3 col = 0;col += lightColor0 * diff.x;col += lightColor1 * diff.y;col += lightColor2 * diff.z;col += lightColor3 * diff.w;return col;
}

Shade4PointLights函数用在顶点着色器的ForwardBase渲染通道上,本函数在每一个顶点被4个点光源照亮时,利用兰伯特光照模型计算出光照的漫反射效果。

5.2 ShaderVertexLightsFull函数

// Used in Vertex pass: Calculates diffuse lighting from lightCount lights. Specifying true to spotLight is more expensive
// to calculate but lights are treated as spot lights otherwise they are treated as point lights.
//本函数用在顶点着色器中,计算出光源产生的漫反射光照效果
//float4 vertex 顶点的位置坐标
//float4 normal 顶点的法线
//float4 lightCount参与光照计算的光源数量
//float4 spotLight 光源是不是聚光灯光源
float3 ShadeVertexLightsFull (float4 vertex, float3 normal, int lightCount, bool spotLight)
{//Unity3D提供的光源位置和光线传播方向在当前摄像机所构成的观察空间中,//所以先把传递进来的顶点坐标变换到观察空间,顶点的法线也乘以model-view矩阵的逆转置矩阵变换到观察空间float3 viewpos = UnityObjectToViewPos (vertex);float3 viewN = normalize (mul ((float3x3)UNITY_MATRIX_IT_MV, normal));//UNITY_LIGHTMODEL_AMBIENT在UnityShaderVariables.cginc文件中定义float3 lightColor = UNITY_LIGHTMODEL_AMBIENT.xyz;for (int i = 0; i < lightCount; i++) {//如果 unity_LightPosition[i]对应的光源是有向平行光,则unity_LightPosition[i].w的值为0,//unity_LightPosition[i].xyz就是光的方向;如果不是有向平行光,w值为1,x、y、z是光源在观察空间//中的位置坐标。总之,toLight就是顶点位置到光源位置的连线的方向向量float3 toLight = unity_LightPosition[i].xyz - viewpos.xyz * unity_LightPosition[i].w;//toLight自身的点积实际上就是顶点到光源的距离的平方float lengthSq = dot(toLight, toLight);// don't produce NaNs if some vertex position overlaps with the light//如果顶点离光源太近了,就微调一个很小的数作为他们的距离lengthSq = max(lengthSq, 0.000001);//求出距离的倒数toLight *= rsqrt(lengthSq);//光源的衰减计算float atten = 1.0 / (1.0 + lengthSq * unity_LightAtten[i].z);if (spotLight){float rho = max (0, dot(toLight, unity_SpotDirection[i].xyz));float spotAtt = (rho - unity_LightAtten[i].x) * unity_LightAtten[i].y;atten *= saturate(spotAtt);}float diff = max (0, dot (viewN, toLight));lightColor += unity_LightColor[i].rgb * (diff * atten);}return lightColor;
}

上述代码中,变量rho由顶点到光源连线方向toLight与聚光灯光源的正前照射方向unity_SpotDirection求点积而成。也就是说,rho为toLight方向与unity_SpotDirection方向的夹角余弦值cos(p)。

5.3 ShadeVertexLights函数

float3 ShadeVertexLights (float4 vertex, float3 normal)
{return ShadeVertexLightsFull (vertex, normal, 4, false);
}

该函数就是转调ShadeVertexLightsFull函数,指定使用4个非聚光灯光源进行光照计算。

5.4 TRANSFORM_TEX宏和TRANSFORM_UV宏

// Transforms 2D UV by scale/bias property
#define TRANSFORM_TEX(tex,name) (tex.xy * name##_ST.xy + name##_ST.zw)// Deprecated. Used to transform 4D UV by a fixed function texture matrix. Now just returns the passed UV.
#define TRANSFORM_UV(idx) v.texcoord.xy

TRANSFORM_TEX即是用顶点中的纹理映射坐标tex与待操作纹理name的Tiling(平铺值)和Offset(偏移值)做一个运算操作。Tilling的默认值为(1,1),Offset的默认值为(0,0),假设指定待采样纹理名为_TextureName,在面板中指定的Tiling和Offset属性的x、y值就分别对应_TextureName_ST变量中的x、y分量和z、w分量。也就是说,如果声明一个名字为_TextureName的sampler2D类型的纹理采样器变量。要使用纹理的Tiling和Offset属性,就必须同时定义一个_TextureName-ST的四维向量(float4、half4、或者fixed4)才可以。

5.5 结构体v2f_vertex_lit

struct v2f_vertex_lit {float2 uv   : TEXCOORD0;fixed4 diff : COLOR0;fixed4 spec : COLOR1;
};

上述代码段的结构体和之前代码段中的VertexLight函数是在VertexLit渲染路径中执行光照计算的。v2f_vertex_lit定义了一个顶点布局格式结构体,该结构体很简单,只用到了一层纹理,另外指定了两种颜色,用来模拟漫反射颜色和镜面反射颜色。

Vertex-Lit是实现最低保真度的光照且不支持实时阴影的渲染路径,最好使用于旧机器或受限制的移动平台上。VertexLit渲染途径通常在一个渲染通路中渲染物体,所有光源的照明都是在顶点着色器上进行计算的。这种渲染途径运行速度最快且有最广泛的硬件支持。但由于所有光照都在顶点着色器中计算的,因此渲染途径不支持大部分逐片元渲染效果,如阴影、法线贴图等。

5.6 VertexLight函数

inline fixed4 VertexLight( v2f_vertex_lit i, sampler2D mainTex )
{fixed4 texcol = tex2D( mainTex, i.uv );fixed4 c;c.xyz = ( texcol.xyz * i.diff.xyz + i.spec.xyz * texcol.a );c.w = texcol.w * i.diff.w;return c;
}

VertexLight是一个简单的顶点光照计算函数,其颜色计算方式就是用顶点漫反射颜色乘以纹理颜色,然后加上纹素的Alpha值与顶点镜面反射颜色,两者之和就是最终的颜色。

5.7 Parallaxoffset函数

// Calculates UV offset for parallax bump mapping
inline float2 ParallaxOffset( half h, half height, half3 viewDir )
{h = h * height - height/2.0;float3 v = normalize(viewDir);v.z += 0.42;return h * (v.xy / v.z);
}

该函数根据当前片元对应的高度图中的高度值h,以及高度缩放系数height和切线空间中片元到摄像机的连线向量,计算到当前片元实际上要使用外观纹理的哪一点的纹理。

5.8 Luminance函数

// Converts color to luminance (grayscale)
inline half Luminance(half3 rgb)
{return dot(rgb, unity_ColorSpaceLuminance.rgb);
}

该函数把一个RGB颜色值转化成亮度值,当前的RGB颜色值基于伽马空间或者线性空间,得到的亮度值有不同的结果。

5.9 LinearRgbToLuminance函数

//把在线性空间中的颜色RGB值转换成亮度值
half LinearRgbToLuminance(half3 linearRgb)
{return dot(linearRgb, half3(0.2126729f,  0.7151522f, 0.0721750f));
}

LinearRgbToLuminance函数是把一个在线性空间中的RGB颜色值转换成亮度值,它实质上就是把一个基于RGB颜色空间的颜色值变换到CIE1931-Yxy颜色空间中得到对应的亮度值y。

六、与HDR及光照贴图颜色编解码相关的工具函数

高动态范围(HDR)光照是一种用来实现超过了显示器所能表现的亮度范围的渲染技术。如果采用8位通道存储每一个颜色的RGB分量,则每个分量的亮度级别只有256种。显然只有256个亮度级别是不足以描述自然界中的亮度差别的情况的,如太阳的亮度可能是一个白炽灯亮度的数千倍,将远远超出当前显示器的亮度表示能力。

假如房间中刺眼的阳光从窗外照射进来,普通渲染方法是把阳光和白色墙的颜色都视为白色,RGB(255,255,255),尽管同是白色,但阳光肯定比白墙刺眼的多。所以应用HDR技术对亮度进行处理,使得它们的亮度能够体现出明显的差异。HDR技术就是把尽可能大的亮度值范围编码到尽可能小的存储空间中。

把大数字范围编码到小数字范围的简单方式,就是把大范围中的数字乘以一个缩小系数。线性映射到小范围上,这种方法虽然能表示的亮度范围扩大了,但却导致了颜色带状阶跃的问题。

所以实际上HDR实现一般遵循以下几步:1.在每个颜色通道是16位或者32位的浮点纹理或者渲染模板上渲染当前的场景。2.使用RGBM、LogLuv等编码方式来节省所需的内存和带宽。3.通过降采样计算场景亮度。4.根据场景亮度值对场景做一个色调映射,将最终颜色值输出到一个每通道8位的RGB格式的渲染目标上。

RGBM是一种颜色编码方式,M(shared multiplier)。为了解决精度不足以存储亮度范围信息的问题,可以创建一个精度更高的浮点渲染目标,但使用高精度的浮点渲染目标会带来另一个问题,即需要更高的内存存储空间和更高的带宽,并且有些渲染硬件无法操作8位精度的渲染目标的速度去操作16位浮点渲染目标。为了解决这个问题,需要采用一种编码方法将这些颜色数据编码成一个能以8位颜色分量存储的数据。编码方式有多种,如RGBM编码,LogLuv编码等。假如有一个给定的包含了RGB颜色分量的颜色值color,定义了一个定义了一个编码后取值“最大范围值”maxRGBM,将其编码成一个含有R、G、B、M这4个分量的颜色值的步骤如Unity3D引擎提供的UnityEncodeRGBM函数所示。

6.1 UnityEncodeRGBM函数

half4 UnityEncodeRGBM (half3 color, float maxRGBM)
{float kOneOverRGBMMaxRange = 1.0 / maxRGBM;const float kMinMultiplier = 2.0 * 1e-2;//将color的RGB分量各自除以maxRGBM的倒数,然后取得最大商float3 rgb = color * kOneOverRGBMMaxRange;float alpha = max(max(rgb.r, rgb.g), max(rgb.b, kMinMultiplier));//用最大商乘以255得到结果值之后,取得大于这个结果值的最小整数,然后将这个值除以255之后,再赋值给变量alphaalpha = ceil(alpha * 255.0) / 255.0;//最小的multiplier控制在0.02// Division-by-zero warning from d3d9, so make compiler happy.alpha = max(alpha, kMinMultiplier);return half4(rgb / alpha, alpha);
}

6.2 DecodeHDR函数

inline half3 DecodeHDR (half4 data, half4 decodeInstructions)
{//当decodeInstruction的w分量为true,即值为1,要考虑HDR纹理中的alpha值对纹理的RGB值的影响,此时的alpha变量值为纹理的alpha值。如果decodeInstruction//的w分量为false,则alpha始终为1// Take into account texture alpha if decodeInstructions.w is true(the alpha value affects the RGB channels)half alpha = decodeInstructions.w * (data.a - 1.0) + 1.0;// If Linear mode is not supported we can skip exponent part//使用伽马工作流#if defined(UNITY_COLORSPACE_GAMMA)return (decodeInstructions.x * alpha) * data.rgb;#else//使用线性工作流//若使用原生的HDR,则使用decodeInstructions的x分量乘以data的RGB分量即可#   if defined(UNITY_USE_NATIVE_HDR)return decodeInstructions.x * data.rgb; // Multiplier for future HDRI relative to absolute conversion.#   elsereturn (decodeInstructions.x * pow(alpha, decodeInstructions.y)) * data.rgb;#   endif#endif
}

6.3 DecodeLightmapRGBM函数


// Decodes HDR textures
// handles dLDR, RGBM formats
// Called by DecodeLightmap when UNITY_NO_RGBM is not defined.
inline half3 DecodeLightmapRGBM (half4 data, half4 decodeInstructions)
{// If Linear mode is not supported we can skip exponent part//在伽马工作流下#if defined(UNITY_COLORSPACE_GAMMA)# if defined(UNITY_FORCE_LINEAR_READ_FOR_RGBM)return (decodeInstructions.x * data.a) * sqrt(data.rgb);# elsereturn (decodeInstructions.x * data.a) * data.rgb;# endif#else//在线性工作流下,以data.a为底数,decodeInstrutions.y为指数,求出幂,然后乘以decodeInstruction.x,作为源颜色的解码系数return (decodeInstructions.x * pow(data.a, decodeInstructions.y)) * data.rgb;#endif
}

该函数是把一个RGGM颜色值解码成一个每通道8位的RGB颜色值。

6.4 DecodeLightmapDoubleLDR函数

//解码一个用dLDR编码的光照贴图
inline half3 DecodeLightmapDoubleLDR( fixed4 color, half4 decodeInstructions)
{// decodeInstructions.x contains 2.0 when gamma color space is used or pow(2.0, 2.2) = 4.59 when linear color space is used on mobile platformsreturn decodeInstructions.x * color.rgb;
}

6.5 DecodeLightMap函数

inline half3 DecodeLightmap( fixed4 color, half4 decodeInstructions)
{
#if defined(UNITY_LIGHTMAP_DLDR_ENCODING)return DecodeLightmapDoubleLDR(color, decodeInstructions);
#elif defined(UNITY_LIGHTMAP_RGBM_ENCODING)return DecodeLightmapRGBM(color, decodeInstructions);
#else //defined(UNITY_LIGHTMAP_FULL_HDR)return color.rgb;
#endif
}

该函数依据UNITY_NO_RGBM是否开启分别转调用了dLDR版本的解码函数和RGBM版本的解码函数。

6.6 unity_Lightmap_HDR变量和DecodeLightmap函数

half4 unity_Lightmap_HDR;inline half3 DecodeLightmap( fixed4 color )
{return DecodeLightmap( color, unity_Lightmap_HDR );
}

6.7 DecodeDirectionalLightmap函数

inline half3 DecodeDirectionalLightmap (half3 color, fixed4 dirTex, half3 normalWorld)
{// In directional (non-specular) mode Enlighten bakes dominant light direction// in a way, that using it for half Lambert and then dividing by a "rebalancing coefficient"// gives a result close to plain diffuse response lightmaps, but normalmapped.// Note that dir is not unit length on purpose. Its length is "directionality", like// for the directional specular lightmaps.//半兰伯特光照模型half halfLambert = dot(normalWorld, dirTex.xyz - 0.5) + 0.5;//w分量用来控制该点上辐射入射度的方向性,即被dominant方向影响的程度return color * halfLambert / max(1e-4h, dirTex.w);
}

光照贴图目前用的比较多而且渲染效果也不错的实现方法是定向光照贴图,它它是原始光照贴图的增强实现。它主要是通过在预处理与实时还原的过程中加入场景中表面的法向量进行运算,进而增强效果。定向光照贴图技术的大致实现方式如下所示:

(1)在采样点处把其所处的半球空间中的辐射入射度用某种方法进行采集并保存。

(2)以某种方法存储额外的且与该辐射入射度相关的法线信息到烘焙所得的光照贴图中。

(3)在运行时的实时渲染过程中,通过光照贴图对片元上的场景辐射入射度,并结合光照信息和方向信息进行还原。

6.8 DecodeRealtimeLightmap函数

inline half3 DecodeRealtimeLightmap( fixed4 color )
{//该函数对实时生成的光照贴图进行解码,Enlighten中间件实时生成的光照贴图//格式不同于一般的unity3d的HDR纹理//例如,烘焙式光照贴图、反射光照探针、还有IBL图像等//Englithen渲染器的RGBM格式纹理是在线性颜色空间中定义颜色,使用了不同的指数操作,要将其还原成RGB颜色需要做以下操作//@TODO: Temporary until Geomerics gives us an API to convert lightmaps to RGBM in gamma space on the enlighten thread before we upload the textures.
#if defined(UNITY_FORCE_LINEAR_READ_FOR_RGBM)return pow ((unity_DynamicLightmap_HDR.x * color.a) * sqrt(color.rgb), unity_DynamicLightmap_HDR.y);
#elsereturn pow ((unity_DynamicLightmap_HDR.x * color.a) * color.rgb, unity_DynamicLightmap_HDR.y);
#endif
}

该函数用来对Enlighten中间件实时生成的光照贴图进行解码。

七、把高精度数据编码到低精度缓冲器的函数

EncodeFloatRGBA函数是把一个在区间[0,1]内的浮点数编码成一个float4类型的RGBA值。这些RGBA值虽然使用float4类型存储,使其每通道的值也是在区间[0,1]内,通常在使用时会将其乘以255后取整为整数。DecodeFloatRGBA是该函数的逆操作。

7.1 EncodeFloatRGBA函数和DecodeFloatRGBA函数

// Encoding/decoding [0..1) floats into 8 bit/channel RGBA. Note that 1.0 will not be encoded properly.
inline float4 EncodeFloatRGBA( float v )
{float4 kEncodeMul = float4(1.0, 255.0, 65025.0, 16581375.0);float kEncodeBit = 1.0/255.0;float4 enc = kEncodeMul * v;enc = frac (enc);enc -= enc.yzww * kEncodeBit;return enc; //返回的是每分量的浮点数数值都在区间[0,1]内的浮点数
}
//把一个float4类型的RGBA纹素值解码成一个float类型的浮点数
inline float DecodeFloatRGBA( float4 enc )
{float4 kDecodeDot = float4(1.0, 1/255.0, 1/65025.0, 1/16581375.0);return dot( enc, kDecodeDot );
}

7.2 EncodeFloatRG函数和DecodeFloatRG函数

// Encoding/decoding [0..1) floats into 8 bit/channel RG. Note that 1.0 will not be encoded properly.
inline float2 EncodeFloatRG( float v )
{float2 kEncodeMul = float2(1.0, 255.0);float kEncodeBit = 1.0/255.0;float2 enc = kEncodeMul * v;enc = frac (enc);enc.x -= enc.y * kEncodeBit;return enc;
}
inline float DecodeFloatRG( float2 enc )
{float2 kDecodeDot = float2(1.0, 1/255.0);return dot( enc, kDecodeDot );
}

相比上面的函数,该函数只是使用了两个通道去进行编码。

7.3 EncodeDepthNormal函数和DecodeDepthNormal函数

inline float4 EncodeDepthNormal( float depth, float3 normal )
{float4 enc;enc.xy = EncodeViewNormalStereo (normal);enc.zw = EncodeFloatRG (depth);return enc;
}inline void DecodeDepthNormal( float4 enc, out float depth, out float3 normal )
{depth = DecodeFloatRG (enc.zw);normal = DecodeViewNormalStereo (enc);
}

EncodeDepthNormal函数调用EncodeViewNormalStereo函数把flaot3类型的法线编码到一个float4类型的前两个分量,调用EncodeFloatRG函数把深度值编码进一个float4类型分量的后两个分量。DecodeDepth则是EncodeDepthNormal函数的逆操作。

(二)unity自带的着色器源码剖析之——————UnityCG.cginc文件(上篇:数学常数、颜色空间常数和函数、顶点布局格式结构体、进行空间变换的函数、HDR级光照贴图编解码相关函数等)相关推荐

  1. (九)unity自带的着色器源码剖析之——————UnityShadowLibrary.cginc文件分析(实时阴影和烘焙阴影、阴影淡化、阴影渗漏处理、PCF阴影过滤解决实时阴影锯齿)

    一.阴影与全局照明系统的关系 Unity3D引擎可以根据宏SHADOWS_SCREEN和LIGHTMAP_ON是否启用决定是否在全局照明系统下对阴影进行混合处理.如果这两个宏同时启用,则HANDLE_ ...

  2. (一)unity自带的着色器源码剖析之——————UnityShaderVariables.cginc文件

    unityShaderVariables.cginc文件中包含大量的工具宏和函数,如变换操作用的矩阵.与摄像机相关的函数.与光照和阴影相关的函数,以及与雾效果相关的函数等.下面依次分析这些工具函数和宏 ...

  3. (八)unity自带的着色器源码剖析之——————Unity3D的全局光照和阴影:下篇(unity3D中的球谐光照和SH球谐函数、unity实时阴影抗锯齿解决方案)

    一.探针基于球谐函数的全局光照 球谐光照是基于预计算辐射度传输理论实现的一种实时渲染技术.预计算辐射度传输技术能够重现在区域面光源照射下的全局照明效果.这种技术通过在运行前对场景中光线的相互作用进行预 ...

  4. (六)unity自带的着色器源码剖析之——————Unity3D的全局光照和阴影:上篇(全局照明GI和局部照明)

    一.全局照明和局部照明 全局照明(global illumination,GI)是用于向三维场景中添加更为逼真的光照效果的一组算法总称.全局算法不仅考虑光源发出的光与被照亮物体之间的关系,即直接照明( ...

  5. 超美响应式自适应引导页带音乐播放器源码

    介绍: 超美响应式自适应引导页带音乐播放器源码,纯html源码,右键记事本修改即可~自带音乐播放器,自带自动打字特效,自带网站存活时间,自带随机ACG背景,自适应响应式网页 网盘下载地址: http: ...

  6. C++ STL : SGI-STL空间配置器源码剖析

    文章目录 空间配置器的概念 SGI-STL空间配置器 一级空间配置器 二级空间配置器 申请空间 补充内存块 从内存池中索要空间 空间回收 内存碎片 外碎片 内碎片 空间配置器的再次封装 空间配置器的概 ...

  7. linux源码剖析四 built-in.o 文件编译生成过程

    vmliux 依赖 vmlinux-deps,而 vmlinux-deps=$(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN), K ...

  8. Google Chrome源码剖析

    http://www.ha97.com/2903.html Google Chrome源码剖析[序] 发表于: Google, Google Chrome, 开源世界, 旧文存档, 编程开发 | 作者 ...

  9. Chrome源码剖析、上--多线程模型、进程通信、进程模型

    Chrome源码剖析.上 原著:duguguiyu. 整理:July. 时间:二零一一年四月二日. 出处:http://blog.csdn.net/v_JULY_v. 说明:此Chrome源码剖析很大 ...

最新文章

  1. 解密FFmpeg播放track mode控制
  2. 0xc0000225无法进系统_电脑无法启动,出现0xc0000225一到错误,该怎样解决!
  3. 熊掌号指数2.0常见问题汇总
  4. GitHub趋势榜第一:超级命令行工具Semantic,比较解析源代码
  5. 两种方法用于检查传入的数字是否为整数
  6. 动力强劲的星型发动机,为何不用在汽车上呢?
  7. bash shell函数的定义及变量的简单说明
  8. java_自定义标签运行原理
  9. 固定于计算机主机,一种便于固定的计算机主机的制作方法
  10. 快速入门 Pinia 状态管理库
  11. LightOJ 1245 - Harmonic Number (II)
  12. android c++标准命名空间demo
  13. python有参装饰器详解_Python 带有参数的装饰器实例代码详解
  14. 图像增强处理之:同态滤波与Retinex算法(二)McCann Retinex和McCann99 Retinex迭代算法
  15. .net pdf转图片_pdf转图片怎么转?看了就知道啦!
  16. echarts 鼠标弹框显示百分比柱状图显示百分比
  17. PageHelper.startPage不生效,失效问题
  18. linux heartbeat rpm,[原]Heartbeat 3.0.3 介绍及rpm
  19. Linux命令详解之 cp
  20. 我不喜欢代码,却为何坚持做程序员(二)

热门文章

  1. html 回复 笑脸图标,CSS3 笑脸图标
  2. AWS AppSync 的基本语句
  3. 数据分析如何赋能社区团购? by彭文华
  4. 面对前端六年历史代码,如何接入并应用ES6解放开发效率
  5. mssql-server 树莓派_树莓派4发布,价格35美元起
  6. JZOJ 4245【五校联考6day2】er
  7. CAD ObjectARX 插入块的代码
  8. 百亿数据,毫秒级返回查询优化
  9. 微信支付,JSAPI支付,APP支付,H5支付,Native支付,小程序支付功能详情以及回调处理
  10. 计算机网络网络适配器的作用是什么原因,网络适配器是什么_网络适配器有什么作用 - 驱动管家...