unity shaderlab 正向渲染 基础之渲染模式 使用正向渲染实现一个漫反射shader

一 基础简介

1.1 光源类型

【平行光】场景中唯一的全局光,光源信息可以影响场景中所有物体。
【环境光】是Edit->Render Setting里面的Ambient Light的值。在Shader中获取它只需要访问全局变量UNITY_LIGHTMODEL_AMBIENT即可。它是全局变量,因此在在哪个Pass里访问都可以。
【点光源】以一个中心点向周围扩散的光源,有衰退。
【聚光灯】像聚光灯一样从一个点向某个方向和范围散发光亮。

1.2 灯光渲染模式(Render Mode)

决定一个灯光是哪种处理模式取决于它的Render Mode,设置方式为点击灯光后,在Inspector中设置Render Mode:
【Not Important】所有非平行光源,会逐顶点或者球谐函数处理
【Important】所有非平行光源,会逐像素处理
【设置任何模式】场景中的平行光源都是按逐像素处理

1.3 渲染模式(LightMode)

渲染模式是关于渲染方式的设置。
渲染模式需要在shader中的 Pass > Tags中设置
pass内的tags有别与subshader中的tags,主要用于渲染模式设置

pass内的tags说明

取值例子说明
Always“LightMode”=“Always”不管是用哪种渲染路径,该pass总是会被渲染。但不计算任何光照
Forwardbase“LightMode”=“ForwardBase”用于向前渲染,该pass会计算环境光,重要的平行光,逐顶点/SH光源和lightmaps
ForwardAdd“LightMode”=“ForwardAdd”用于向前渲染,该pass会计算额外的逐像素光源,每个pass对应一个光源
Deferred“LightMode”=“Deferred”用于向前渲染,该pass会渲染G缓冲,G-buffer
ShadowCaster“LightMode”=“ShadowCaster”把物体的深度信息渲染到阴影映射纹理(shadowmap)或一张深度纹理中,用于渲染产生阴影的物体
ShadowCollector“LightMode”=“ShadowCollector”用于收集物体阴影到屏幕坐标Buff里
PrepassBase用于遗留的延迟渲染,该pass会渲染法线和高光反射的指数部分
PrepassFinal用于遗留的延迟渲染,该pass通过合并纹理、光照和自发光来渲染得到最后的颜色
Vertex、VertexLMRGBM和VertexLM用于遗留的顶点照明渲染

二 正向渲染(Forward Rendering )

2.1 向前渲染的基础说明

【说明】正向渲染即正向渲染是物体渲染顺序的一种。
【原理】先着色后深度测试。即将舞台中所有光照与有效物体进行着色计算后,再使用深度测试决定像素有效性。
【优点】容易理解,实现方便。显卡兼容性较好。
【缺点】能损耗较大。因为在最终输出的时候那些没有通过深度测试的像素就白计算了。
【正向渲染与延迟渲染的区别】
正向渲染与延迟渲染都是关于光照计算的渲染方法。他们的最终目的都是进行光照处理。他们最大的区别是正向渲染先计算光照信息再处理深度计算。而延迟渲染正相反,先进行包括深度在内的着色计算,再处理光照计算。
【向前渲染中的三种处理光照的方式】1. 逐顶点处理,2.逐 像素处理 3,球谐函数(Spherical Harmonics,SH)SH光处理

2.2 向前渲染的 Pass

向前渲染的代码必须写在两种Pass中。ForwardBase专门用于处理平行光、环境光,使用逐顶点/SH处理,而ForwardAdd用于处理其他光源,使用逐像素处理。

2.2.1 ForwardBase

【作用】:该Pass中会计算最重要的平行光源、逐顶点/SH光源LightMaps和环境光。只有ForwardBase中处理的第一个平行光可以有阴影效果。另外有多少光源会按照逐顶点光源来处理要取决于光源的属性【Pixel Light Count】,例如Pixel Light Count 是4,那么就只有4个光源会按逐点光源处理,其他只能按SH光源处理
【启用方法】:pass中 tag 设置 “LightMode”=“ForwardBase”
使用:可以使用_WorldSpaceLightPos0,_LightColor0等属性

2.2.2 ForwardAdd

【作用】:该Pass中会计算诸如点光源锥光源等其他的逐像素光源。可以有多个ForwardAdd的Pass,每个符合标准的光源都会调用一次相匹配的ForwardAdd Pass。如果场景中有m个光源,shader中有n个ForwardAdd Pass,那么关于光源的渲染就是m*n次。如果shader中没有光源相关的ForwardAdd处理,则物体不受该光源的影响
【启用方法】:pass中要使用 tag 为 “LightMode”=“ForwardAdd”

2.2.3 阴影处理
  1. 处理光照阴影必须添加#pragma multi_compile_fwdbase
  2. 在顶点输入参数中使用 SHADOW_COORDS(2)来定义阴影通道
  3. 在顶点处理器里调用TRANSFER_SHADOW(v2f)处理片元像素
  4. 在片元着色器调用SHADOW_ATTENUATION(v2f) 返回的就是这个像素是否存在阴影中。
2.3 向前渲染的实现
2.3.1 实例说明

场景中包含一个唯一的平行光,4个点光源。其中环境光、平行光在ForwardBase 的Pass中逐顶点计算(frag片元着色器中还可以计算光照贴图等)。4个设置为Important的点光源 在ForwardAdd的Pass中逐像素计算,每个点光源执行一次ForwardAdd 的Pass。

2.3.2 使用步骤

0,在shader脚本中分别实现ForwardBase、ForwardAdd两个pass

//1,引入必要宏
	#pragma multi_compile_fwdbase 
    // unity 的光源相关工具函数
    #include "UnityCG.cginc"
    #include "Lighting.cginc"
    #include "AutoLight.cginc"
//2, 在ForwardBase中使用SHADOW_COORDS(2)、TRANSFER_SHADOW(v2f)、SHADOW_ATTENUATION(color) 处理平行光自然光。
//2,在ForwardAdd中通过计算得到worldLightDir、atten衰减等信息处理像素

1,点击场景中需要启用的光源,然后在Inspactor窗口中设置RenderMode 为Important或Auto,如果设置为Not Important 就不会被shader处理了

2,然后创建Shader和metaril并在shader中写入如下代码,再将shader赋给metaril

Shader "Custom/3d"
{
    //此处设置对unity可见的参数
    Properties
    {
        //贴图入口
        _MainTex ("Texture", 2D) = "white" {}
        _ParallelColor ("平行光、环境光颜色叠加", Color) = (1,1,1,1)
        _DiffuseColor ("其他光源 颜色叠加", Color) = (1,1,1,1)
    }
    SubShader
    {
        //usb shader的队列,此处设置为非透明物体
        Tags { "RenderType"="Opaque" }
        LOD 100
        //正向渲染 用于处理平行光、环境光、顶点光的pass
        Pass
        {
            // 设置渲染方式 
            // 用于向前渲染,ForwardBase会计算环境光,重要的平行光,逐顶点/SH光源和lightmaps
            Tags{"LightMode" = "ForwardBase"}
            CGPROGRAM
            // 定义顶点、片元着色器
            #pragma vertex vert
            #pragma fragment frag
            // 用于得到光源衰减等信息
            #pragma multi_compile_fwdbase 
            // unity 的光源相关工具函数
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"
            // shader变量,用于对接unity、在shader中使用
            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _DiffuseColor;
            fixed4 _ParallelColor;

            // 程序传入到顶点着色器的数据,包括获取贴图uv、顶点位置、法线信息、阴影坐标
            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 pos : SV_POSITION;
                float3 normal : NORMAL;
                //SHADOW_COORDS这个宏后面的参数是指第几个通道,要改变投影的颜色话必须要占用一个通道,不要和其他的冲突
                SHADOW_COORDS(2)
            };
            // 顶点着色器  
            v2f vert (appdata_base v)//appdata_base是内置的参数结构体
            {
                v2f o;
                //将顶点从模型坐标转为裁剪坐标
                o.pos = UnityObjectToClipPos(v.vertex);
                // 应用在unity中设置的uv坐标的信息
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                //获取世界坐标中的法线信息
                o.normal = mul(v.normal,(float3x3)unity_WorldToObject);
                // 阴影处理  在顶点处理器里调用TRANSFER_SHADOW
                TRANSFER_SHADOW(o);
                return o;
            }
            //片元着色器
            fixed4 frag (v2f i) : SV_Target
            {
                // 获取uv与贴图处理后的当前像素信息
                fixed4 col = tex2D(_MainTex, i.uv);
                // 世界灯光方向 = 灯光的世界坐标归一化
                float3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz );
                //模型法线
                float3 normal = normalize(i.normal);
                // 环境光 = 通过 #pragma multi_compile_fwdbase  获取的换进骨光颜色,透明度为1
                fixed4 ambient = fixed4(UNITY_LIGHTMODEL_AMBIENT.xyz, 1);

                // 漫反射 = 灯光颜色 * 0-1之间(dot(像素世界法线, 世界灯光方向))
                // y = saturate(x)的作用:如果x < 0,y = 0。如果x > 1,y = 1。否则 y = x
                // dot:点乘, ab为向量,dot(a, b)  = a1b1+a2b2+...
                fixed4 diffuse = fixed4(_LightColor0.xyz * saturate(dot(normal, worldLightDir)), 1);
                // 计算像素颜色  漫反射与环境光融合
                col = col * (ambient + diffuse);
                // 阴影 = 通过 #pragma multi_compile_fwdbase 片元着色器调用SHADOW_ATTENUATION,返回的就是这个像素是否存在阴影中。
                fixed shadow = SHADOW_ATTENUATION(i) * _ParallelColor;
                // 最终颜色 = 阴影 * 当前像素颜色
                col = shadow * col;
                return col;
            }
            ENDCG
        }

        //正向渲染 用于处理点光源锥光源等的pass
        Pass{
            
            // ForwardAdd 该Pass中会计算诸如点光源锥光源等其他的逐像素光
            Tags {"LightMode" = "ForwardAdd"}
            Blend One One
            CGPROGRAM
            // 定义顶点、片元着色器
            #pragma vertex vert
            #pragma fragment frag
            // 用于得到光源衰减等信息
            #pragma multi_compile_fwdadd
            // unity 的光源相关工具函数
            #include "unitycg.cginc"
            #include "lighting.cginc"
            #include "autolight.cginc "

            
            fixed4 _DiffuseColor;
            // 顶点传入片元的数据、包括像素坐标、法线信息、uv
            struct v2f{
                float4 pos : SV_POSITION;
                float3 normalWorld : NORMAL;
                float4 uv : TEXCOORD0;
            };
            // 顶点着色器 
            v2f vert(appdata_base v)
            {
                v2f o;
                // 将像素坐标从模型空间转为裁剪空间
                o.pos = UnityObjectToClipPos(v.vertex);
                // 世界法线 
                o.normalWorld = mul(v.normal, (float3x3)unity_WorldToObject);
                // uv = 世界uv
                o.uv = mul(v.vertex, unity_ObjectToWorld);
                return o;
            }
            // 片元着色器
            fixed4 frag (v2f i) : SV_Target
            {
                // 衰减
                fixed atten;
                // 灯管反向
                fixed3 worldLightDir;
                // 分别处理平行光的衰减,与其他光源的衰减
                // 如果是平行光源 用 normalize(_WorldSpaceLightPos0.xyz);就可以获取到方向
                // 如果是其他光源 需要用normalize(_WorldSpaceLightPos0.xyz - i.uv.xyz);来获取
                #ifdef USING_DIRECTIONAL_LIGHT 
                    //平行光的衰减
                    // 世界灯光方向 = 归一化(世界灯光坐标)
                    worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                    // 衰减 = 1
                    atten = 1;
                #else
                    //点光源的衰减
                    // 世界灯光方向 = 归一化(世界灯光坐标 - 像素uv)
                    worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                    // 光的坐标 
                    float3 lightCoord = mul(unity_WorldToLight, i.uv).xyz;
                    // 计算衰减 
                    atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord)).UNITY_ATTEN_CHANNEL;
                #endif
                // 漫反射 = 灯光颜色 * 衰减 * dot(灯光方向,模型法线)
                fixed3 diffuse = _LightColor0.xyz * atten * saturate(dot(worldLightDir,normalize( i.normalWorld)));
                return fixed4(diffuse, 1) * _DiffuseColor;
            }
            ENDCG
        }
    }
    FallBack "Specular"
}

不错的参考文章:
https://www.cnblogs.com/xiegaosen/p/10886404.html
https://www.jianshu.com/p/80a932d1f11e
https://blog.csdn.net/yangxuan0261/article/details/90182143

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千年奇葩

从来没受过打赏,这玩意好吃吗?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值