TA不一样(七)
前言:PBR做为shader一个比较重要的方面本节对其做下总结1.PBR简介PBRPhysically Based Rendering基于物理的渲染是一套遵循物理光学规律的渲染技术体系核心用于在计算机中模拟真实世界的材质光照表现。PBR的组成漫反射与反射光线在物体表面作用的最基本的两个表现光线的能量守恒漫反射光线反射光线入射光线金属高反射度导致只有很少的光线能进入金属表面之下发生散射这也就是所谓的“金属光泽菲涅耳(Fresnel)随着入射光线相对与表面法线的夹角越大反射率就越大微表面物体的表面不可能是绝对平滑使用属性“光泽度”(Gloss)或者“光滑度(Smoothness)它的反属性为“粗造度(Roughness),来描述微表面反射光线的能量守恒反射光线由于微表面的作用造成了不同方向的反射但总能量维持不变PBR着色可以表示更多更复杂的材质特征表面细节物体粗糙度区别明显的金属和绝缘体物体的浑浊程度菲涅尔现象:不同角度有不同强度的反射光半透明物体多层混合材质清漆效果其它更复杂的表面特征PBR的发展历史Lambert计算漫反射SmithPhong一种传统的光照模型Cook-Torrance该模型考虑在同一景物中不同材料和不同光源的相对亮度Oren Nayarh主要对粗糙表面的物体建模比如石膏、沙石、陶瓷等Schlick简化了Phong模型的镜面反射中的指数运算GGX将微平面反射模型推广到表面粗糙的半透明材质从而能够模拟类似于毛玻璃的粗糙表面的透射效果迪斯尼原则的BRDF奠定了后续游戏行业和电影行业PBR的方向和标准现阶段的BxDF(材质模型)2.Unity PBR在现代图形学中为了符合人眼感知和物理光照计算Unity 默认使用线性空间Linear Space‌ 进行渲染。‌纹理/颜色输入‌通常存储在 ‌sRGBGamma 空间‌ 中因为人眼对暗部更敏感sRGB 能节省带宽。‌光照计算‌必须在 ‌线性空间‌ 中进行物理公式如 IL⋅NIL⋅N 在线性下才成立。‌矛盾‌如果直接把 sRGB 的颜色值拿去算光照结果会偏亮或偏暗。Unity 会自动处理纹理采样把Gamma空间下的数据转换到Linear空间。Project Settings - Player - Color Space中可以设置空间为Gammar或Linear3.Custom PBR双向反射分布函数(Bidirectional Reflectance Distribution FunctionBRDF),BRDF计算是在一个给定了属性的不透明表面上每个单独的光线对最终的反射光的贡献量在Unity中实现自己的PBR如果使用BRDF公式可以简化为PBR输出颜色漫反射 镜面反射直接光漫反射直接光镜面反射 间接光漫反射间接光镜面反射代码实现Shader MyCustom/MyPBRTest { Properties { // 贴图 _MainTex (Texture, 2D) white {} // 颜色 _Tint (Tint, Color) (1, 1, 1, 1) // Gamma空间下的金属度金属度贴图要转为srgb [Gamma]_Metallic (Metallic, Range(0, 1)) 0 // 光滑度不需要加Gamma空间贴图计算时已经进行转换 _Smoothness (Smoothness (Metallic.a), Range(0, 1)) 0.6 } SubShader { Tags { RenderTypeOpaque } LOD 100 Pass { Tags { // 只有定义它才能访问unity的一些内置方法 LightMode ForwardBase } CGPROGRAM #pragma vertex vert #pragma fragment frag // make fog work #pragma multi_compile_fog #include UnityCG.cginc #include UnityStandardBRDF.cginc #include UnityStandardUtils.cginc struct appdata { float4 vertex: POSITION; float2 uv: TEXCOORD0; float3 normal: NORMAL; }; struct v2f { float2 uv: TEXCOORD0; float3 normal: TEXCOORD1; float3 worldPos: TEXCOORD2; UNITY_FOG_COORDS(1) float4 vertex: SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; float4 _Tint; float _Metallic; float _Smoothness; v2f vert (appdata v) { v2f o; // 转换一些常用的数据:uv世界法线世界位置 o.vertex UnityObjectToClipPos(v.vertex); o.uv TRANSFORM_TEX(v.uv, _MainTex); o.normal normalize(UnityObjectToWorldNormal(v.normal)); o.worldPos mul(unity_ObjectToWorld, v.vertex).xyz; UNITY_TRANSFER_FOG(o,o.vertex); return o; } // 直接光漫反射使用disney模型(kd:漫反射系数albedo:反射系数perceptualRoughness:感知粗糙度 float3 _directLightDiffuse(float kd, float3 albedo, float nv, float nl, float lh, float perceptualRoughness) { // 使用disney漫反射的时候需要 * albedo / PI //float3 disneyDiffuse DisneyDiffuse(nv, nl, lh, perceptualRoughness) * albedo * UNITY_PI; // Unity 使用的版本比disney较暗所以可以不出PI但为保持能量守恒镜面反射中要乘回去 float3 disneyDiffuse DisneyDiffuse(nv, nl, lh, perceptualRoughness) * albedo/* / UNITY_PI*/; // 叠加颜色 float3 result kd * disneyDiffuse * nl * _LightColor0.rgb; return result; } // 直接光镜面反射大部分模型使用内置函数 float3 _directLightSpecular(float3 albedo, float nv, float nl, float lh, float nh, float roughness) { // D 项 (GGX) 法线分布函数 float D GGXTerm(nh, roughness); // G 项 几何函数微表面遮蔽效果 float G SmithJointGGXVisibilityTerm(nl, nv, roughness); // F 项 菲涅尔 float3 F0 lerp (unity_ColorSpaceDielectricSpec.rgb, albedo, _Metallic); float3 F FresnelTerm(F0, lh); float3 dgf D * G * F; // Cook - torrance BRDF //float3 result dgf / (4 * nv * nl) * nl * _LightColor0 .rgb; // Unity对应漫反射中需要额外乘PI float3 result dgf /*/ (4 * nv * nl)*/ * nl * _LightColor0 .rgb * UNITY_PI; return result; } // 间接光漫反射,使用球谐函数ShadeSH9()近似计算,传入世界空间法线 float3 _indirectLightDiffuse(float kd, float3 albedo, float3 normal) { // normal 是 worldspace空间坐标系 float3 iblDiffuse ShadeSH9(float4(normal, 1)); float3 result kd * albedo * iblDiffuse; return result; } // 间接光镜面反射 //实时计算每个像素对周围环境的镜面反射积分极其昂贵。Unity 使用 Epic Games 提出的“分离求和近似”将复杂的积分拆分为两部分预计算: // 1.针对不同的 ‌粗糙度Roughness‌预先对环境贴图进行模糊处理 // 2.预计算菲涅尔项 F0和粗糙度对镜面反射强度的影响存储为一张 2D 纹理。 float3 _indirectLightSpecular(float kd, float3 albedo, float roughness, float perceptualRoughness, float nv, float3 viewDir, float3 normal) { //-----------预先对环境贴图进行模糊处理------------ // 1. 关于使用粗糙度来采样 cubemap的mipmap // Unity的粗糙程度和采样的mipmap不是线性的转换公式: // mip r * ( 1.7 - 0.7r) float mip_roughness perceptualRoughness * (1.7 - 0.7 * perceptualRoughness); // 根据视线方向和法线求反射方向准备采样cubemap float3 reflectVec reflect(-viewDir, normal); // mip_roughness (0~1) 映射到实际的mip层级UNITY_SPECCUBE_LOD_STEPS是常量默认为6 float mip mip_roughness * UNITY_SPECCUBE_LOD_STEPS; // UNITY_SAMPLE_TEXCUBE_LOD 是采样函数 // cubemap采样是三线性插值1.从两张临近的mipmap各做一次2次线性插值2.将结果再次插值 float4 rgbm UNITY_SAMPLE_TEXCUBE_LOD(unity_SpecCube0, reflectVec, mip); // 使用DecodeHDR将颜色从HDR解码因为rgbm是HDR格式颜色会超过1 float3 specular DecodeHDR(rgbm, unity_SpecCube0_HDR); //----------镜面积分--------------- // 2. 使用 sufaceReduction 来拟合间接光照在粗糙物体上过亮的问题 // unity 使用拟合函数来实时计算查找图(Look Up Table, LUT)的数据 float surfaceReduction 1.0 / (roughness * roughness 1); float oneMinusReflectivity kd; // unity 考虑掠射镜面反射 (grazing specular reflections) float grazingTerm saturate(_Smoothness (1 - oneMinusReflectivity)); float3 F0 lerp (unity_ColorSpaceDielectricSpec.rgb, albedo, _Metallic); float3 fresnelTerm FresnelLerp(F0, grazingTerm, nv); float3 result specular * surfaceReduction * fresnelTerm; return result; } fixed4 frag (v2f i) : SV_Target { // 准备 shader 计算所需要的参数 float3 normal normalize(i.normal); // n: 需要加 normalize将顶点法线正确插值 float3 lightDir normalize(_WorldSpaceLightPos0.xyz); // l: 世界空间坐标系下 float3 viewDir normalize(_WorldSpaceCameraPos.xyz - i.worldPos); // 世界空间坐标系下 float3 halfVector normalize(lightDir viewDir);// h: 半角向量 float nl max(saturate(dot(normal, lightDir)), 0.0000001); float nv max(saturate(dot(normal, viewDir)), 0.0000001); float lh max(saturate(dot(lightDir, halfVector)), 0.0000001); float nh max(saturate(dot(normal, halfVector)), 0.0000001); // 感知粗糙度 float perceptualRoughness 1 - _Smoothness; float roughness perceptualRoughness * perceptualRoughness; roughness max(roughness, 0.002); // smoothness 为1还是有高光点 // 计算albedo和kd float3 albedo tex2D(_MainTex, i.uv) * _Tint; float kd OneMinusReflectivityFromMetallic(_Metallic); // 直接光总和 float3 directLightColor _directLightDiffuse(kd, albedo, nv, nl, lh, perceptualRoughness) _directLightSpecular(albedo, nv, nl, lh, nh, roughness); // 间接光总和 float3 indirectLightColor _indirectLightDiffuse(kd, albedo, normal) _indirectLightSpecular(kd, albedo, roughness, perceptualRoughness, nv, viewDir, normal); float3 finalColor directLightColor indirectLightColor; // apply fog UNITY_APPLY_FOG(i.fogCoord, finalColor); return float4(finalColor, 1); } ENDCG } } }

相关新闻