【Shader笔记10】更复杂的光照-Unity的光源类型

光源类型

  1. 分类:平行光、点光源、聚光灯、面光源
  2. 属性:位置、方向、颜色、强度、衰减
  3. 面光源仅在烘培时才可发挥作用

PS:需要在Scene视图中开启光照才能看到预览光源是如何影响场景中的物体
在这里插入图片描述

在前向渲染中处理不同的光源类型

先判断光源类型,获得光源方向,之后处理光源的衰减

  1. 先定义Base Pass
  • 设置Pass的渲染路径标签Tags{“LightMode”=“ForwardBase”}
  • 注意要使用编译指令 #pragma multi_compile_fwdbase才可以让光照变量被正确赋值
  • _WorldSpaceLightPos0 获得平行光的方向
  • _LightColor0已经是颜色和强度相乘之后的结果
  • 定义衰减值(平行光可认为是没有衰减的)
  1. 为场景中其他逐像素光源定义Additional Pass
  • 设置Pass的渲染路径标签Tags{“LightMode”=“ForwardAdd”}
  • 使用编译指令 #pragma multi_compile_fwdadd 保证我们在Additional Pass 中访问到正确的光照变量
  • 使用Blend(混合)命令,让Additional Pass计算得到的光照结果可以在帧缓存中与之前的光照结果进行叠合,不然会直接覆盖掉之前的光照结果
  • 可以直接把Base Pass的顶点和片元着色器代码复制到Additional Pass中,去掉Base Pass中环境光、自发光、逐顶点光照、SH光照的部分,再添加一些对不同光源类型的支持

注意

  • 先判断当前处理的逐像素光源的类型
  • 使用#ifdef指令判断是否定义了USING_DIRECTIONAL_LIGHT,如果当前前向渲染Pass处理的光源是平行光,那么Unity的底层渲染引擎就会定义USING_DIRECTIONAL_LIGHT
  • 如果是平行光的话,光源方向直接由 _WorldSpaceLightPos0.xyz 得到
  • 如果是点光源或者聚光灯,那么 _WorldSpaceLightPos0.xyz 表示的是世界空间下的光源位置
  • 光源方向需要用这个位置 - 世界空间下的顶点位置

处理不同光源的衰减

  • 同样判断是否定义了USING_DIRECTIONAL_LIGHT
  • 如果是平行光,衰减值为1.0
  • 若是其他类型光源,Unity使用一张纹理作为查找表(Lookup Table,LUT),以再片元着色器中得到光源的衰减:首先得到光源空间下的坐标,然后使用该坐标对衰减纹理进行采样得到衰减值
// Upgrade NOTE: replaced ‘_LightMatrix0’ with ‘unity_WorldToLight’ 报错
在对应的Pass中添加 #include “AutoLight.cginc”。 

效果:
在这里插入图片描述

  1. Base Pass和Additional Pass的调用(光源的渲染顺序)
  • 前向渲染中Unity决定了哪些光源是逐像素光,哪些是逐顶点或SH光

  • 默认情况下,新创建一个光源,它的Render 设置为Auto(Unity在背后判断处理方式:逐像素、逐顶点或SH方式)

  • Edit -> Project Setting ->Quality ->Pixel Light Count 中的数值,代表默认情况下一个物体可以接受最亮的平行光外的n个逐像素光照。每个光源会调用一次Additional Pass。

  • 帧调用器(Frame Debugger)可以来查看场景的绘制过程 (Frame Debugger 在Unity的Window->Analysis里可以找到)。例如下面:

  1. 在第一个渲染事件中,Unity首先清除颜色、深度和模板缓冲
  2. 第二个渲染事件中,Unity利用Base Pass,将平行光的光照渲染到帧缓存中
  3. 后面三个渲染事件中,利用Additional Pass依次将3个点光源的光照应用到物体上,得到最后的渲染结果
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 处理点光源顺序:按照重要程度排序,即取决于点光源的颜色、强度、距离物体远近
  • 如果物体不在光源的光照范围内,Unity不会为这个物体调用Pass来处理这个光源(调用相关的渲染事件)
  • 把光源的Render Mode设为Not Important,Unity不会把该光源当成逐像素光来处理

PS:当把所有的光源设置为Not Important时(包括平行光),物体仅会显示环境光的光照结果,如下图:
在这里插入图片描述

Unity的光照衰减

  1. Unity使用一张纹理作为查找表来在片元着色器中计算逐像素光照的衰减
  2. 缺点:
  • 需要预处理得到采样纹理,而且纹理的大小也会影响衰减的精度
  • 数据一旦存储到查找表中,就无法使用其他数学公式来计算衰减
  1. 用于光照衰减的纹理
  • Unity内部使用了一张_LightTexture0的纹理来计算光源衰减(如果我们对该光源使用了cookie,那么衰减查找纹理是_LightTextureB0)
  • _LightTexture0的(0,0)明了于光源位置重合的点的衰减值,(1,1)表明在光源空间中距离最远的点的衰减
  • _LightTexture0可以把顶点从世界空间变换到光源空间
    (通过_LightTexture0变换矩阵得到的,P188_LightTexture0本身即为世界空间到光源空间的变换矩阵,可以用于采样cookie和光强衰减纹理)
    ,补充:

— 只需_LightTexture0和世界空间中的顶点相乘即可得到光源空间中的相应位置

float3 lightCoord = mul(_LightMatrix0,float4(i.worldPos,1)).xyz;  

— 使用这个坐标的模的平方对衰减纹理进行采样,得到衰减值:

fixed atten = tex2D(_LightTexture0,dot(lightCoord,lightCoord).rr).UNITY_ATTEN_CHANNEL;  

(通过dot函数来得到光源空间中顶点距离的平方)
(宏UNITY_ATTEN_CHANNEL得到衰减纹理中衰减值所在分量,以得到最终的衰减值)

  1. 使用数学公式计算衰减

线性衰减:

float distance = length(_WorldSpaceLightPos0.xyz - i.worldPosition.xyz);  
atten = 1.0/distance;  
Shader "Custom/ForwardRendering"
{
    Properties
    {
        _Diffuse("Diffuse",Color)=(1,1,1,1)
		_Specular("Specular",Color)=(1,1,1,1)
		_Gloss("Gloss",Range(8.0,256))=20
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass   //Base Pass
        {
			Tags{"LightMode"="ForwardBase"}

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
			#pragma multi_compile_fwdbase
			//编译指令 #pragma multi_compile_fwdbase
			//保证我们在shader中使用光照衰减等光照变量可以被正确赋值
			
			#include "Lighting.cginc"
            struct a2v
            {
                float4 vertex : POSITION;
				float3 normal:NORMAL;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;//裁剪空间下的顶点坐标
				float3 worldNormal : TEXCOORD0;
				float3 worldVertex : TEXCOORD1;
            };

            fixed4 _Diffuse;
			fixed4 _Specular;
			float _Gloss;

            v2f vert (a2v v)
            {
                v2f o;
                o.vertex = mul(UNITY_MATRIX_MVP,v.vertex);

				o.worldNormal= UnityObjectToWorldNormal(v.normal);//法线 模型空间-世界空间
				o.worldVertex = mul(unity_ObjectToWorld,v.vertex).xyz;//顶点坐标 模型空间-世界空间

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
				fixed3 normalDir = normalize(i.worldNormal);
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
				fixed3 lightDir = normalize(_WorldSpaceLightPos0);
				fixed3 viewDir = normalize(UnityWorldSpaceViewDir (i.worldVertex) );
				fixed3 halfDir = normalize(lightDir + viewDir);

				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max( 0, dot(normalDir,lightDir) );   
				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow ( max ( 0,dot(normalDir,halfDir)) ,_Gloss);
				//_LightColor0 已经是颜色和强度相乘之后的结果

				fixed atten=1.0; //由于平行光可以认为是没有衰减的,因此这里直接令衰减值为1.0

                return fixed4(ambient + (diffuse + specular) * atten ,1.0);
            }
            ENDCG
        }

		Pass   //Additional Pass
        {
			Tags{"LightMode"="ForwardAdd"}
			Blend One One

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

			#include "Lighting.cginc"
			#include "AutoLight.cginc" //不加_LightMatrix0会报错

			#pragma multi_compile_fwdadd
			//编译指令 #pragma multi_compile_fwdadd
			//保证我们在Additional Pass 中访问到正确的光照变量
            struct a2v
            {
                float4 vertex : POSITION;
				float3 normal:NORMAL;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;//裁剪空间下的顶点坐标
				float3 worldNormal : TEXCOORD0;
				float3 worldVertex : TEXCOORD1;
            };

            fixed4 _Diffuse;
			fixed4 _Specular;
			float _Gloss;


            v2f vert (a2v v)
            {
                v2f o;
                o.vertex = mul(UNITY_MATRIX_MVP,v.vertex);

				o.worldNormal= UnityObjectToWorldNormal(v.normal);//法线 模型空间-世界空间
				o.worldVertex = mul(unity_ObjectToWorld,v.vertex).xyz;//顶点坐标 模型空间-世界空间

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
				//先判断当前处理的逐像素光源的类型
				//使用#ifdef指令判断是否定义了USING_DIRECTIONAL_LIGHT
				//如果当前前向渲染Pass处理的光源是平行光,那么Unity的底层渲染引擎就会定义USING_DIRECTIONAL_LIGHT
				//如果是平行光的话,光源方向直接由_WorldSpaceLightPos0.xyz得到
				//如果是点光源或者聚光灯,那么_WorldSpaceLightPos0.xyz表示的是世界空间下的光源位置
				//光源方向需要用这个位置 - 世界空间下的顶点位置
				#ifdef USING_DIRECTIONAL_LIGHT
					fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
				#else
					fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldVertex.xyz);
				#endif

				fixed3 normalDir = normalize(i.worldNormal);
				fixed3 viewDir = normalize(UnityWorldSpaceViewDir (i.worldVertex) );
				fixed3 halfDir = normalize(lightDir + viewDir);

				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max( 0, dot(normalDir,lightDir) );   
				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow ( max ( 0,dot(normalDir,halfDir)) ,_Gloss);
				//_LightColor0 已经是颜色和强度相乘之后的结果

				//处理不同光源的衰减
                #ifdef USING_DIRECTIONAL_LIGHT
                    fixed atten = 1.0;
                #else
                    #if defined (POINT)
                        float3 lightCoord = mul(unity_WorldToLight, float4(i.worldVertex, 1)).xyz;
                        fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
                    #elif defined (SPOT)
                        float4 lightCoord = mul(unity_WorldToLight, float4(i.worldVertex, 1));
                        fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
                    #else
                        fixed atten = 1.0;
                    #endif
                #endif

                return fixed4( (diffuse + specular) * atten ,1.0);
            }
            ENDCG
        }
    }
	FallBack"Specular"
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值