内部PBR流程
pbr材质的光滑反射度流程
- Albedo Texture的rgb通道对应光照运算方程中的diffuse color;a通道对应于Alpha;
- Speculer Texture的rgb通道对应光照运算方程的F0;a通道对应光照运算方程的Smoothness;
其oneMinusReflectivity = 1-SpecularStrength(specColor);//SpecularStrength函数是来获取rgb通道最大值;
oneMinusReflectivity 用于计算IBL reflection的菲涅尔项,具体可看源码,但是我不知道算法的原理==;
pbr材质的金属粗糙度流程
- Albedo Texture的rgb通道为albedo;a通道对应于Alpha;
其diffuse color需要通过以下方法来计算:
oneMinusReflectivity = unity_ColorSpaceDielectricSpec.a*(1-metallic);
diffColor = albedo * oneMinusReflectivity; - Aetallic Texture的r通道对应金属度metallic;a通道对应光照运算方程的Smoothness;
其Speculer Color(即F0)需要通过以下方法来计算:
specColor = lerp (unity_ColorSpaceDielectricSpec.rgb, albedo, metallic);
注意:unity_ColorSpaceDielectricSpec在unity内部的值为half4(0.04, 0.04, 0.04, 1.0 - 0.04) //linear space
unity内部的environmentBRDF
- 对于IBL部分的BRDF,unity好像采用的拟合的方法来进行计算,在unreal中存在LUT与拟合两种方法;
- 对于unity采用的拟合方法,其F项计算方法为:
half grazingTerm = saturate(smoothness + (1-oneMinusReflectivity));
FresnelLerp (specColor, grazingTerm, nv);
其它项为:
half surfaceReduction;
# ifdef UNITY_COLORSPACE_GAMMA
surfaceReduction = 1.0-0.28*roughness*perceptualRoughness; // 1-0.28*x^3 as approximation for (1/(x^4+1))^(1/2.2) on the domain [0;1]
# else
surfaceReduction = 1.0 / (roughness*roughness + 1.0); // fade \in [0.5;1]
# endif
阴影投射通道的编写
- 暂时先copy自unity官方代码,在其内部包括了bias、normal bias等参数的处理,即light组件下,Realtime Shadows的那些参数,有空在仔细分析。
渲染流程中的各种问题
MSAA、HDR、_CameraDepthTexture的问题
- 在windows平台下,可以直接使用_CameraDepthTexture,并且MSAA与HDR一起开好像也没问题;windows下,查看frame debugger,会发现先渲染_CameraDepthTexture深度图,然后在渲染两张分辨率不同的ShadowMap,然后是Screen Space Shadow的生成,最后才开始正常的渲染流程;(在延迟渲染中,_CameraDepthTexture可以免费获取~);
注意:只有含有shadowcaster pass的物体才可以渲染进_CameraDepthTexture,并且只能是不透明物体( render queue <= 2500);参考Unity官方文档
- 在移动平台下,需要显示设置Camera的depthTextureMode,即:
came.depthTextureMode = DepthTextureMode.Depth;
;此时,可以直接使用_CameraDepthTexture,并且MSAA与HDR一起开好像也没问题;查看frame debugger,会发现先渲染_CameraDepthTexture深度图,然后在渲染一张ShadowMap,然后才开始正常的渲染流程; - 问题来了!,Unity官方说开启HDR就不支持MSAA了,High Light System插件的文档也说开启HDR不支持MSAA,并且开启MSAA会导致无法获取_CameraDepthTexture,参考文档High Dynamic Range Rendering。这一部分问题涉及到硬件,但是我按照前面的1、2使用,没有出现这种问题。如果有大佬清楚还请指导一下==。
- 所以说解决方案应该是,关闭MSAA,使用后处理的AA,这样就可以开启HDR并使用_CameraDepthTexture了;
内部便利函数及宏
获取法线自纹理:
UnpackNormal(tex2D(_BumpMap, i.uv));
half3 UnpackScaleNormal(half4 packednormal, half bumpScale);
获取world tangent、binormal、normal向量:
float3 normalWorld = UnityObjectToWorldNormal(v.normal);
float4 tangentWorld = float4(UnityObjectToWorldDir(v.tangent.xyz), v.tangent.w);
half3x3 tangentToWorld = CreateTangentToWorldPerVertex(half3 worldNormal, half3 worldTangent, half tangentSign)
{
// For odd-negative scale transforms we need to flip the sign
half sign = tangentSign * unity_WorldTransformParams.w;
half3 binormal = cross(normal, tangent) * sign;
return half3x3(tangent, binormal, normal);
}
o.tangent = tangentToWorld[0];
o.binormal = tangentToWorld[1];
o.normal = tangentToWorld[2];
linear与gamma空间转换:
inline bool IsGammaSpace();
inline half3 GammaToLinearSpace (half3 sRGB);
inline half3 LinearToGammaSpace (half3 linRGB);
unity内部阴影的使用
阴影的投射只要有shadow caster通道即可;
阴影接受的计算:
方法一:
#pragma multi_compile_fwdbase
#include “AutoLight.cginc”
SHADOW_COORDS(1) // put shadows data into TEXCOORD1
TRANSFER_SHADOW(o)
fixed shadow = SHADOW_ATTENUATION(i);
方法二:
#pragma multi_compile_fwdbase
// after CGPROGRAM;
#include "AutoLight.cginc"
// in v2f struct;
LIGHTING_COORDS(0,1) // replace 0 and 1 with the next available TEXCOORDs in your shader, don't put a semicolon at the end of this line.
// in vert shader;
TRANSFER_VERTEX_TO_FRAGMENT(o); // Calculates shadow and light attenuation and passes it to the frag shader.
//in frag shader;
float atten = LIGHT_ATTENUATION(i); // This is a float for your shadow/attenuation value, multiply your lighting value by this to get shadows. Replace i with whatever you've defined your input struct to be called (e.g. frag(v2f [b]i[/b]) : COLOR { ... ).
方法三:
#pragma multi_compile_fwdbase
#include "AutoLight.cginc"
UNITY_LIGHTING_COORDS(6,7)
UNITY_TRANSFER_LIGHTING(o, v.uv1);
UNITY_LIGHT_ATTENUATION(atten, i, s.posWorld);
Shader编写坑点
正常normal与detail normal之间的处理:
- 方法一:首先进行normal与detail normal的解压,然后由mask在两者之间进行lerp操作获取final normal,最后对final normal进行normalize操作;
注意:其计算空间为tangent space;
- 方法二:detai normal是在正常normal的基础上叠加发现,因此就需要根据正常normal与detai normal获取叠加后的temp normal,然后由mask在正常normal与temp normal之间进行lerp操作获取final normal,最后对final normal进行normalize操作;
获取temp normal的方法为:
temp normal = normalize(normal.xy + detail.xy, normal.z*detail.z);
实际中可使用的方法有好几种,各游戏及引擎使用的方法也不一致。可参考文章 Blending in Detail,该文章进行了详细的归纳,并且提供了各种方法的优缺点及其代码!点赞!
Unity shader计算所使用MVP归一化后发现z的范围为[1,0]
按照unity的顶点变换流程,mul( UNITY_MATRIX_MVP, v.vertex)的结果进行归一化后应位于NDC空间,即xyz的范围都为[-1, 1]。
但是在shader手动进行归一化后发现z的范围为[1,0];感觉很奇怪,而如果采用脚本中获取相机的vp矩阵计算,归一化后其深度范围刚好为[-1,1];
事实证明shader中的VP矩阵与相机中的VP矩阵根本不一致;
具体可参考这里;
原文为:
Note: On DX11/12, PS4, XboxOne and Metal, the Z buffer range is 1–0 and UNITY_REVERSED_Z is defined. On other platforms, the range is 0–1.
原文中的宏被废弃了,文档也不更新,呵呵呵。。。
另外使用unity内部的_CameraDepthTexture时,其范围是(0,1);
获取shader中所使用VP:
由于DX平台与OpenGL平台的差异性,unity使用了GL类来进行两者的统一,同样使用该库来进行统一的渲染,因此我们可以使用该库来获取shader中VP矩阵:
Matrix4x4 projectionMatrix = GL.GetGPUProjectionMatrix(mCamera.projectionMatrix, true);
unity_MatrixVP = projectionMatrix * mCamera.worldToCameraMatrix;
参考这里
总结
没事多看看unity内部源码,想用什么从里面摘什么==