Unity基于自定义管线实现经典经验光照模型

一,简介

SRP管线的好处就是任何渲染效果都可以自己去实现,坏处就是任何渲染效果都要自己去实现。

本文在Catlike Coding 实现的【Custom SRP 2.5.0】基础上给出几个经典光照模型的Shader实现,Catlike Coding在这教程中使用的是PBR光照模型,网上常见的实现范例都是基于Unity预设的管线,这边给出这个教程框架下的实现。

二,环境

Unity :2022.3.18f1

CRP Library :14.0.10

URP基本结构 : Custom SRP 2.5.0

三、实现

光照模型主要考虑的如何计算影响到物体上的光,常见的光照区分方式有Phong 模型中区分环境光(Ambient)、漫反射(Diffuse)和高光反射(Specular)。 或是全局光照模型中区分直接光照(Direct Illumination)和间接光照(Indirect Illumination) 。在渲染中有时候还要考虑物体的自发光 ,以及其他地方过来的折射光。

在工程实现角度来讲,拆解光照有助于理解实现思路,毕竟有时候我们并不关心光学理论去解释为什么要这么做。

1,Lambert(兰伯特)光照模型

Lambert光照模型是最简单的光照模型,主要考虑环境光和漫反射光。在这个模型中,漫反射光是主要处理的。漫反射光的处理思想也很简单,只要将各个光源照射到物体表面的颜色叠加起来即可。所以简单来说最终的颜色应该是:

(环境光 + 各个光源的颜色影响) 混合 物体自身颜色

当然,各个光源的颜色影响不是简单的取色,Lambert在处理的时候会将物体表面的法线考虑进去,所以一般的Lambert的简单实现都是类似这样:

光照颜色 * (表面法线与入射光方向的夹角)

公式为:

N 为法线,L为入射光方向,k为其他影响参数比如光源距离、自定义调节等等。

在该管线中shader实现:

复制代码
Shader "Custom/LambertShader"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _NormalTex ("NormalTex", 2D) = "white" {}
    }
    SubShader
    {
        Pass
        {

            Tags { "LightMode" = "CustomLit"}


            HLSLPROGRAM
             #pragma target 4.5
            
            //没错,这个就是代码过于耦合的下场,为了实现一个简单的shader我不得不把一堆引用加进来
            #include "Custom RP/ShaderLibrary/Common.hlsl"
            #include "Custom RP/ShaderLibrary/Surface.hlsl"
            #include "Custom RP/ShaderLibrary/Shadows.hlsl"
            #include "Custom RP/ShaderLibrary/Light.hlsl"
            #include "Custom RP/ShaderLibrary/BRDF.hlsl"
            #include "Custom RP/ShaderLibrary/GI.hlsl"
            #include "Custom RP/ShaderLibrary/Lighting.hlsl"
            #include "Custom RP/ShaderLibrary/LitInput.hlsl"

            struct Attributes {
	            float3 positionOS : POSITION;
	            float3 normalOS : NORMAL;
                float4 tangentOS : TANGENT;
	            float2 baseUV : TEXCOORD0;

            };

            struct Varyings {
	            float4 positionCS_SS : SV_POSITION;
	            float3 positionWS : VAR_POSITION;
	            float3 normalWS : VAR_NORMAL;
	            float2 baseUV : VAR_BASE_UV;
                float4 tangentWS : VAR_TANGENT;
            };


            #pragma vertex LitPassVertex
	        #pragma fragment LitPassFragment

            TEXTURE2D(_MainTex);
            SAMPLER(sampler_MainTex);
            TEXTURE2D(_NormalTex);
             SAMPLER(sampler_NormalTex);

            UNITY_INSTANCING_BUFFER_START(UnityPerMaterial)
            UNITY_DEFINE_INSTANCED_PROP(float4, _MainTex_ST)
            UNITY_DEFINE_INSTANCED_PROP(float4, _Color)

            UNITY_INSTANCING_BUFFER_END(UnityPerMaterial)

            Varyings LitPassVertex(Attributes input)
	        {
		        Varyings output;

		        output.positionWS = TransformObjectToWorld(input.positionOS);
		        output.positionCS_SS = TransformWorldToHClip(output.positionWS);
                float4 baseST = INPUT_PROP(_MainTex_ST);
                output.baseUV = input.baseUV * baseST.xy + baseST.zw;
                output.normalWS = TransformObjectToWorldNormal(input.normalOS);
                output.tangentWS = float4(TransformObjectToWorldDir(input.tangentOS.xyz), input.tangentOS.w);
		        return output;
	        }
            
            //获取环境光,这里只是简单采样
            float3 GetAmbientLight(float3 normalWS)
            {
		        float4 coefficients[7];
		        coefficients[0] = unity_SHAr;
		        coefficients[1] = unity_SHAg;
		        coefficients[2] = unity_SHAb;
		        coefficients[3] = unity_SHBr;
		        coefficients[4] = unity_SHBg;
		        coefficients[5] = unity_SHBb;
		        coefficients[6] = unity_SHC;
		        return max(0.0, SampleSH9(coefficients, normalWS));
            }

	        float4 LitPassFragment(Varyings input) : SV_TARGET
            {		
                float4 color = INPUT_PROP(_Color);
	            float4 baseColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.baseUV) * color;
                float4 normalE = SAMPLE_TEXTURE2D(_NormalTex, sampler_NormalTex, input.baseUV);
                float3 normal = DecodeNormal(normalE, 1);
                float3 normalWS = NormalTangentToWorld(normal,input.normalWS, input.tangentWS);

                float3 diffuse = 0;
                for (int i = 0; i < GetDirectionalLightCount(); i++) {

                    DirectionalLightData data = _DirectionalLightData[i];
                    diffuse += data.color.rgb * max(0, dot(normalWS, data.directionAndMask.xyz));
		        }

                float3 ambient = GetAmbientLight(normalWS);

	            return float4(baseColor.xyz * (diffuse + ambient),1) ;

	        }


            ENDHLSL

        }
 
    }

}

效果:

左边是Lambert的实现,右边是教程里的PBR材质shader,当然参数调整了,金属度和粗糙度都归零了

2,Phong光照模型

Phong 光照模型在 Lambert 模型的基础上增加了 高光反射,模拟光线在光滑表面的镜面反射效果。

为此,高光反射的计算公式为:

(光线反射方向 x 视线方向)的s次方

s为反射系数,一般都是自定义,数值越大反射范围越小。

对应代码:

复制代码
float3 viewDir = normalize(_WorldSpaceCameraPos - input.positionWS);
float3 specular = 0;
for (int i = 0; i < GetDirectionalLightCount(); i++) {

    DirectionalLightData data = _DirectionalLightData[i];
    diffuse += data.color.rgb * max(0, dot(normalWS, data.directionAndMask.xyz));

    float3 reflectDir = reflect( -data.directionAndMask.xyz,normalWS);
    float spec = pow(max(0, dot(viewDir, reflectDir)), _Shininess);
    specular = spec;
}

如果我们将反射强度直输会得到这样的结果:

复制代码
            return float4(specular,1) ;

这个模型的核心是如何获取反射光的强度,拿到反射光的强度后,后面怎么使用就比较自由了。比如尝试增强这片区域灯光颜色。

复制代码
float3 diffuse = 0;
float3 specular = 0;
for (int i = 0; i < GetDirectionalLightCount(); i++) {

    DirectionalLightData data = _DirectionalLightData[i];
    diffuse += data.color.rgb * max(0, dot(normalWS, data.directionAndMask.xyz));

    float3 reflectDir = reflect( -data.directionAndMask.xyz,normalWS);
    float spec = pow(max(0, dot(viewDir, reflectDir)), _Shininess);
    specular = spec *  data.color.rgb;
}

float3 ambient = GetAmbientLight(normalWS);

return float4(baseColor.xyz * (diffuse + ambient)+ specular,1) ;

效果:

(中间是Phone,右边是Lambert)

或是混合,效果就比较柔和一些:

复制代码
  return float4(baseColor.xyz * (diffuse + ambient + specular),1) ;

效果:

可以再添加一个参数用来控制反射强度:

复制代码
.......
                    specular = spec *  data.color.rgb * _SpecIntensity;
.......


	            return float4(baseColor.xyz * (diffuse + ambient + specular),1) ;

效果:

3. Blinn-Phong 光照模型

Phong模型的改进版,本质上还是调整高光反射,与Phong光照模型的区别主要在引入半角向量,简化了高光反射的计算。

半角向量的计算为:

( 光线方向 + 视线方向)的归一化

对应代码:

复制代码
	        float4 LitPassFragment(Varyings input) : SV_TARGET
            {		
                float4 color = INPUT_PROP(_Color);
	            float4 baseColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.baseUV) * color;
                float4 normalE = SAMPLE_TEXTURE2D(_NormalTex, sampler_NormalTex, input.baseUV);
                float3 normal = DecodeNormal(normalE, 1);
                float3 normalWS = NormalTangentToWorld(normal,input.normalWS, input.tangentWS);
                float3 viewDir = normalize(_WorldSpaceCameraPos - input.positionWS);

                float3 diffuse = 0;
                float3 specular = 0;
                for (int i = 0; i < GetDirectionalLightCount(); i++) {

                    DirectionalLightData data = _DirectionalLightData[i];
                    diffuse += data.color.rgb * max(0, dot(normalWS, data.directionAndMask.xyz));

                    float3 halfDir = normalize(data.directionAndMask + viewDir);  
                    float spec = pow(max(0, dot(normalWS, halfDir)), _Shininess);
                    specular = spec *  data.color.rgb * _SpecIntensity;
		        }

                float3 ambient = GetAmbientLight(normalWS);

	            return float4(baseColor.xyz * (diffuse + ambient + specular),1) ;

	        }

效果:

参考资料:

【传统经验光照模型详解】

【Unity中Shader的Lambert光照的实现】

【4种基本光照模型】

【理解Phong光照模型】

【Unity Shader 光照模型详解】

【比较全面也比较详细的光照模型】

相关推荐
辻弋2013 天前
五年前的旅行视频糊成马赛克?Video2X用Real-ESRGAN逐帧重建细节,但只支持Windows、集显用户建议直接放弃
服务器·数据库·windows·游戏引擎·电脑
Martina_03214 天前
AI生成3D场景导入 Unity/Unreal 后,NPC 总串层?用6步检查导航高度与跨层连接
人工智能·游戏·数学建模·3d·unity·自然语言处理·aigc
小贺儿开发4 天前
Unity 局域网遥控幻灯片展示工具 3.0
科技·http·unity·工具·ppt·通信·网页
WarPigs6 天前
Unity对象不应该使用null传播的问题
unity
每日出拳老爷子7 天前
【VR】【Unity】抓走转放系列21|一天搭完门和抽屉工具槽:小场景按这个次序拼装
unity·vr·驱动·排错·snap zone
淡海水7 天前
02-02-原理篇-分代GC
算法·unity·c#·游戏引擎·.net·gc
小贺儿开发8 天前
Unity 文物新生 会讲故事的文物展墙
人工智能·科技·unity·ai·视频·互动·演示
淡海水8 天前
02-03-原理篇-大对象堆
unity·c#·游戏引擎·.net·gc
WarPigs9 天前
Unity在Create菜单添加创建自定义脚本模板选项
unity
郝学胜-神的一滴9 天前
C++ Templates 01:拨开迷雾,读懂C++模板经典著作
开发语言·c++·程序人生·游戏·游戏引擎·软件构建