如果你只做能力范围之内的事,你就永远不会有进步!|

陈侠云

园龄:2年10个月粉丝:1关注:1

实现一个前向渲染的Phong模型(一)

标准Phong模型实现

Shader "Unlit/PhongJian"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Shininess ("Shininess", Range(0.01, 100)) = 1.0    // 高光亮度对比度
        _SpecIntensity("SpecIntensity",Range(0.01,5)) = 1.0 // 高光亮度控制
        _AmbientColor ("Ambient Color", Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            Tags{"LightMode" = "ForwardBase"}
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
			#pragma multi_compile_fwdbase
			#include "UnityCG.cginc"
			#include "AutoLight.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 texcoord : TEXCOORD0;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                float3 normal_dir : TEXCOORD1;
                float3 pos_world : TEXCOORD2;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _LightColor0;
            float _Shininess;
            float4 _AmbientColor;
            float _SpecIntensity;

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                o.normal_dir = normalize(mul(float4(v.normal, 0), unity_WorldToObject).xyz);
                o.pos_world = mul(unity_ObjectToWorld, v.vertex).xyz;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                half4 base_color = tex2D(_MainTex, i.uv);
                
                half3 normal_dir = normalize(i.normal_dir);
                half3 view_dir = normalize(_WorldSpaceCameraPos.xyz - i.pos_world);
                half3 light_dir = normalize(_WorldSpaceLightPos0.xyz);

                half NdotL = dot(normal_dir, light_dir);
                half3 diffuse_color = max(0, NdotL) * _LightColor0.xyz * base_color;

                half3 reflect_dir = reflect(-light_dir, normal_dir);
                half RdotV = dot(reflect_dir, view_dir);
                half3 spec_color = pow(max(0, RdotV), _Shininess) * _LightColor0.xyz * _SpecIntensity;

                half3 final_color = diffuse_color + spec_color + _AmbientColor.xyz;
                
                return fixed4(final_color,1);
            }
            ENDCG
        }
    }
    
    Fallback "Diffuse" 
}

AO贴图

Ambient Occlusion(环境遮挡贴图)简称AO贴图,模拟物体之间所产生的阴影,在不打光的时候增加体积感。也就是完全不考虑光线,单纯基于物体与其他物体越接近的区域,受到反射光线的照明越弱这一现象来模拟现实照明(的一部分)效果。
image

遮挡贴图用于提供关于模型哪些区域应接受高或低间接光照的信息。间接光照来自环境光照和反射,因此模型的深度凹陷部分(例如裂缝或折叠位置)实际上不会接收到太多的间接光照。

遮挡纹理贴图通常由 3D 应用程序使用建模器或第三方软件直接从 3D 模型进行计算。

遮挡贴图是灰度图像,其中以白色表示应接受完全间接光照的区域,以黑色表示没有间接光照。有时,对于简单的表面而言,这就像灰度高度贴图一样简单(例如前面高度贴图示例中显示的凸起石墙纹理)。

在其他情况下,生成正确的遮挡纹理稍微复杂一些。例如,如果场景中的角色穿着罩袍,则罩袍的内边缘应设置为非常低的间接光照,或者完全没有光照。在这些情况下,遮挡贴图通常将由美术师制作,使用 3D 应用程序基于模型自动生成遮挡贴图。

代码实现

half4 ao_color = tex2D(_AOMap, i.uv);
half3 final_color = (diffuse_color + spec_color + _AmbientColor.xyz) * ao_color;

image
左:无AO 右:有AO

点光源衰减公式计算

                half3 light_dir = normalize(_WorldSpaceLightPos0.xyz - i.pos_world);
                half distance = length(_WorldSpaceLightPos0.xyz - i.pos_world);
                half range = 1.0 / unity_WorldToLight[0][0];
                half attuenation = saturate((range - distance) / range);

法线贴图

每个顶点有法线和切线数据,其中切线的走向模型导入unity时根据UV坐标中的U来确定的(并不是模型自带),下图蓝色是法线,绿色是切线,红色是副切线
image

法线、切线、副切线

                o.normal_dir = normalize(mul(float4(v.normal, 0), unity_WorldToObject).xyz);
                o.tangent_dir = normalize(mul(unity_ObjectToWorld, float4(v.tangent.xyz, 0)).xyz);
                o.binormal_dir = normalize(cross(o.normal_dir, o.tangent_dir)) * v.tangent.w;          // tangent.w 是为了处理不同平台下的翻转问题,不是1就是-1

PC端上,采用DXT/BC对法线贴图压缩,通道信息会变更,所以要用UnpackNormal函数进行解码,顺便把法线图范围从0-1转为-1到1
image
解码后,会得到如下图所示的贴图,其中大量蓝色说明法线仍然是平面朝上的(0,0,1),红色是(0,1,0)代码沿着平面,这样在特定角度看,我们就能感受到它的凹凸不平,但平行于平面看还是会感觉很平,这是因为没有它的3D数据。
image
_NormalIntensity法线xy偏移强度,默认为1,下面会将将顶点法线改变为贴图上的位置

                half3 normal_data = UnpackNormal(normalMap);
                normal_data.xy = normal_data.xy * _NormalIntensity;
                
                half3 normal_dir = normalize(i.normal_dir);
                half3 tangent_dir = normalize(i.tangent_dir);
                half3 binormal_dir = normalize(i.binormal_dir);
                float3x3 TBN = float3x3(tangent_dir, binormal_dir, normal_dir);
                normal_dir = normalize(mul(normal_data.xyz, TBN));
                //normal_dir = normalize(tangent_dir * normal_data.x * _NormalIntensity + binormal_dir * normal_data.y * _NormalIntensity + normal_dir * normal_data.z);
                

右图:无法线贴图 左图:有法线贴图
image

完整代码提供
点击查看代码
Shader "Unlit/PhongJian"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _NormalMap("NormalMap",2D) = "bump"{}
        _AOMap ("Texture", 2D) = "white" {}                 // AO贴图
        _SpecMask ("Texture", 2D) = "white" {}              // 粗糙度贴图反向得到
        _Shininess ("Shininess", Range(0.01, 100)) = 1.0    // 高光亮度对比度
        _SpecIntensity("SpecIntensity",Range(0.01,5)) = 1.0 // 高光亮度控制
        _NormalIntensity("NormalIntensity", Range(0, 5)) = 1// 法线xy偏移强度
        _AmbientColor ("Ambient Color", Color) = (1,1,1,1)  
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            Tags{"LightMode" = "ForwardBase"}
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
			#pragma multi_compile_fwdbase
			#include "UnityCG.cginc"
			#include "AutoLight.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 texcoord : TEXCOORD0;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                float3 normal_dir : TEXCOORD1;
                float3 pos_world : TEXCOORD2;
                float3 tangent_dir : TEXCOORD3;
                float3 binormal_dir : TEXCOORD4;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _LightColor0;
            float _Shininess;
            float4 _AmbientColor;
            float _SpecIntensity;
            sampler2D _AOMap;
            sampler2D _SpecMask;
            sampler2D _NormalMap;
            float _NormalIntensity;

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                o.normal_dir = normalize(mul(float4(v.normal, 0), unity_WorldToObject).xyz);
                o.tangent_dir = normalize(mul(unity_ObjectToWorld, float4(v.tangent.xyz, 0)).xyz);
                o.binormal_dir = normalize(cross(o.normal_dir, o.tangent_dir)) * v.tangent.w;          // tangent.w 是为了处理不同平台下的翻转问题,不是1就是-1
                o.pos_world = mul(unity_ObjectToWorld, v.vertex).xyz;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                half4 base_color = tex2D(_MainTex, i.uv);
                half4 ao_color = tex2D(_AOMap, i.uv);
                half4 spec_mask = tex2D(_SpecMask, i.uv);
                half4 normalMap = tex2D(_NormalMap, i.uv);
                half3 normal_data = UnpackNormal(normalMap);
                normal_data.xy = normal_data.xy * _NormalIntensity;
                
                half3 normal_dir = normalize(i.normal_dir);
                half3 tangent_dir = normalize(i.tangent_dir);
                half3 binormal_dir = normalize(i.binormal_dir);
                float3x3 TBN = float3x3(tangent_dir, binormal_dir, normal_dir);
                normal_dir = normalize(mul(normal_data.xyz, TBN));
                //normal_dir = normalize(tangent_dir * normal_data.x * _NormalIntensity + binormal_dir * normal_data.y * _NormalIntensity + normal_dir * normal_data.z);
                
                half3 view_dir = normalize(_WorldSpaceCameraPos.xyz - i.pos_world);
                half3 light_dir = normalize(_WorldSpaceLightPos0.xyz);

                half NdotL = dot(normal_dir, light_dir);
                half3 diffuse_color = max(0, NdotL) * _LightColor0.xyz * base_color;

                half3 reflect_dir = reflect(-light_dir, normal_dir);
                half RdotV = dot(reflect_dir, view_dir);
                half3 spec_color = pow(max(0, RdotV), _Shininess) * _LightColor0.xyz * _SpecIntensity * spec_mask.rgb;

                half3 final_color = (diffuse_color + spec_color + _AmbientColor.xyz) * ao_color;
                
                return fixed4(final_color,1);
            }
            ENDCG
        }
        
        Pass
        {
            Tags{"LightMode" = "ForwardAdd"}
            Blend One One
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
			#pragma multi_compile_fwdadd
			#include "UnityCG.cginc"
			#include "AutoLight.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 texcoord : TEXCOORD0;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                float3 normal_dir : TEXCOORD1;
                float3 pos_world : TEXCOORD2;
                float3 tangent_dir : TEXCOORD3;
                float3 binormal_dir : TEXCOORD4;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _LightColor0;
            float _Shininess;
            float4 _AmbientColor;
            float _SpecIntensity;
            sampler2D _AOMap;
            sampler2D _SpecMask;
            sampler2D _NormalMap;
            float _NormalIntensity;

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                o.normal_dir = normalize(mul(float4(v.normal, 0), unity_WorldToObject).xyz);
                o.tangent_dir = normalize(mul(unity_ObjectToWorld, float4(v.tangent.xyz, 0)).xyz);
                o.binormal_dir = normalize(cross(o.normal_dir, o.tangent_dir)) * v.tangent.w;          // tangent.w 是为了处理不同平台下的翻转问题,不是1就是-1
                o.pos_world = mul(unity_ObjectToWorld, v.vertex).xyz;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                half4 base_color = tex2D(_MainTex, i.uv);
                half4 ao_color = tex2D(_AOMap, i.uv);
                half4 spec_mask = tex2D(_SpecMask, i.uv);
                half4 normalMap = tex2D(_NormalMap, i.uv);
                half3 normal_data = UnpackNormal(normalMap);
                normal_data.xy = normal_data.xy * _NormalIntensity;
                
                half3 normal_dir = normalize(i.normal_dir);
                half3 tangent_dir = normalize(i.tangent_dir);
                half3 binormal_dir = normalize(i.binormal_dir);
                float3x3 TBN = float3x3(tangent_dir, binormal_dir, normal_dir);
                normal_dir = normalize(mul(normal_data.xyz, TBN));
                //normal_dir = normalize(tangent_dir * normal_data.x * _NormalIntensity + binormal_dir * normal_data.y * _NormalIntensity + normal_dir * normal_data.z);
                
                half3 view_dir = normalize(_WorldSpaceCameraPos.xyz - i.pos_world);

                #if defined (DIRECTIONAL)
                half3 light_dir = normalize(_WorldSpaceLightPos0.xyz);
                half attenuation = 1;
                #elif defined (POINT)
                half3 light_dir = normalize(_WorldSpaceLightPos0.xyz - i.pos_world);
                half distance = length(light_dir);
                half range = 1.0 / unity_WorldToLight[0][0];
                half attenuation = saturate((range - distance) / range);
                #endif
                
                half NdotL = dot(normal_dir, light_dir);
                half3 diffuse_color = max(0, NdotL) * _LightColor0.xyz * base_color * attenuation;

                half3 reflect_dir = reflect(-light_dir, normal_dir);
                half RdotV = dot(reflect_dir, view_dir);
                half3 spec_color = pow(max(0, RdotV), _Shininess) * _LightColor0.xyz * _SpecIntensity * spec_mask.rgb * attenuation;

                half3 final_color = (diffuse_color + spec_color) * ao_color;        // add中不计算环境光
                
                return fixed4(final_color,1);
            }
            ENDCG
        }
    }
    
    Fallback "Diffuse" 
}

##### 最终效果

image

参考文档

  1. https://mp.weixin.qq.com/s?__biz=MzU0MDcxOTc5MA==&mid=2247493155&idx=1&sn=856fc57a251058d31695f840c88c277e&chksm=fb3649e2cc41c0f4e479fc23d468ef3491f55b3e3ee6f109205b92a873b0d5776ae8474faf26&scene=27
  2. https://zhuanlan.zhihu.com/p/655471214

本文作者:陈侠云

本文链接:https://www.cnblogs.com/chenxiayun/p/18130433

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   陈侠云  阅读(29)  评论(0编辑  收藏  举报
//雪花飘落效果
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起