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 光照模型详解】

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

相关推荐
心疼你的一切4 小时前
Unity异步编程神器:Unitask库深度解析(功能+实战案例+API全指南)
深度学习·unity·c#·游戏引擎·unitask
呆呆敲代码的小Y6 小时前
【Unity 实用工具篇】 | Book Page Curl 快速实现翻书效果
游戏·unity·游戏引擎·u3d·免费游戏·翻书插件
AC梦19 小时前
unity中如何将UI上的字高清显示
ui·unity
小贺儿开发1 天前
Unity3D 智慧城市管理平台
数据库·人工智能·unity·智慧城市·数据可视化
June bug2 天前
【领域知识】休闲游戏一次发版全流程:Google Play + Apple App Store
unity
星夜泊客2 天前
C# 基础:为什么类可以在静态方法中创建自己的实例?
开发语言·经验分享·笔记·unity·c#·游戏引擎
dzj20212 天前
PointerEnter、PointerExit、PointerDown、PointerUp——鼠标点击物体,则开始旋转,鼠标离开或者松开物体,则停止旋转
unity·pointerdown·pointerup
心前阳光2 天前
Unity 模拟父子关系
android·unity·游戏引擎
在路上看风景2 天前
26. Mipmap
unity