首页 > 其他分享 >实现一个前向渲染的Phong模型(一)

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

时间:2024-04-17 09:03:52浏览次数:23  
标签:normalize normal color xyz 渲染 half3 Phong 前向 dir

标准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

标签:normalize,normal,color,xyz,渲染,half3,Phong,前向,dir
From: https://www.cnblogs.com/chenxiayun/p/18130433

相关文章

  • 关于渲染优化记录
    项目中需要使用渲染优化,因为打开村庄或者关卡,显卡温度太高70-80摄氏度,需要优化,当然渲染上优化有提升帧率,提升游戏稳定性(流畅度),而该优化侧重性能方法上分为项目开发时的设置优化,开发时的逻辑优化,发包后运行时的设置优化 关于项目开发时的现有方法: 放置CullDistanceVolume. ......
  • 3dmax在线渲染怎么取消?3dmax怎么关闭云渲染
    ​在线渲染,无论是通过云渲染服务还是渲染农场,已经成为众多3dmax动画制作者的首选方式来执行渲染任务。然而,如果在渲染过程中需要禁用这一在线渲染功能,该怎么操作呢?接下来,让我们一起探讨如何关闭这一功能的详细步骤。3dmax在线渲染关闭过程动画客户端1、3dmax文件在动画客户端......
  • 节省时间和资源:了解如何最大化渲染农场的排队管理效率
    ​在3D渲染领域,时间的价值无可替代。随著3D艺术家与制作工作室不断挑战技术极限,对高效计算资源的渴求空前增长,渲染农场因此成为了渲染任务中不可或缺的力量。其核心在于排队系统——这一动态且复杂的结构负责安排和最优化渲染任务的执行顺序与时间,确保了渲染效率和资源的充分利用......
  • nuxt3_使用pinia实现服务器端渲染状态管理
    目录安装nuxt3的pinia包修改nuxt.config.ts的配置在项目中创建pinia仓库创建本地的测试服务器用于测试ssr在nuxt项目中创建服务器端请求接口在/article/[id]路由页面中实现服务器端渲染使用postman向http://localhost:3000/article/95发送请求修改路由配置实现页面数据缓存安装nu......
  • 《Vue.js设计与实现》 第七章 渲染器的设计
    7.1渲染器与响应系统的结合最简单的渲染器及其使用//渲染器functionrenderer(domString,container){container.innerHTML=domString}//使用letcount=1renderer(`<h1>${count}</h1>`,document.getElementById('app'))渲染器与响应式系统结合constcount......
  • 3d动画如何连续渲染?云渲染批量渲染3d动画
    在3D动画的生产过程中,从多个角度渲染同一场景既是时间消耗大户,也对设计人员的技术能力提出了高要求。好在云渲染技术的进步为这一挑战提供了高效的解决办法。通过利用云渲染服务进行批量渲染,设计师们可以大幅节约时间并提升工作效率。3d动画如何连续渲染方法一:批处理渲染1、点......
  • 动画渲染多少钱一秒?云渲染一分钟动画费用
    动画渲染的费用因其复杂度和技术要求而异,云渲染服务则提供了一种高效的解决方案。一般而言,渲染一分钟动画的费用可能从几百到几千元不等,具体取决于使用的资源和渲染质量。下面来看看相关费用吧。动画渲染多少钱一秒?参考:工作室代渲染预计每秒价格:200元到2000元三维动画的费用......
  • css 选择器 和浏览器渲染
    1、!important;2、Id选择器3、类选择器属性选择器伪类选择器4、元素选择器伪元素选择器(::before::after)(a,b,c)算法aid选择器个数这b 类选择器属性选择器伪类选择器和c 元素选择器伪元素选择器和CSS浏览器渲染:1、样式声明:所有自定义的样式和浏览器默认的样式,......
  • 动画渲染属于哪个专业?动画渲染用云渲染快吗
    动画渲染是影视制作关键环节,它依赖于计算机软件将动画设计转换成栩栩如生的图像。动画渲染专家需熟练掌握多种渲染方法,以确保产出的效果既高质量又高效率。随着技术进步,云渲染作为一种创新服务,正在逐步影响渲染速度和流程。动画渲染到底属于何种领域?而云渲染技术能否真的显著加快......
  • blender怎么用GPU渲染?blender GPU云渲染推荐
    ​在三维建模和渲染领域,Blender以其强大的功能和免费开源的特点广受好评。GPU渲染作为提升渲染效率的关键技术,越来越受到用户的关注。本文将详细介绍如何在Blender中设置并利用GPU进行渲染,以及探索其云渲染的可能性,助力用户高效完成渲染任务。blender开启gpu渲染教程方法一:1、......