【Unity Shader】(1)兰伯特模型 逐顶点光照 和 逐像素光照 分别实现 漫反射光照模型

使用逐顶点光照在unityShader中实现漫反射效果

  • 漫反射公式——兰伯特模型

    首先需要了解基本光照模型中,漫反射(Diffuse)的

    兰伯特模型
    1
    兰伯特模型 共有4个参数

  • 入射光线的颜色和强度—— C light

  • 材质的漫反射系数—— M diffuse

  • 表面法线—— n

  • 光源方向—— I

为了避免 n 和 I 的点积为负值,我们需要使用max来操作。但在Unity Shader中,我们可以使用 saturate(x)函数达到相同的效果。

其中,x可以是操作的标量或矢量,即float、float2、float3等类型
saturate会把传进来的x截取在[0,1]的范围内,如果x是一个矢量,那么就会对它的每一个分量进行截取操作。

  • 光照原理

在宏观上,渲染可以分为一下两个:
1、决定像素是否可见;
2、决定像素的光照计算。

在这一片博客,我们主要探讨第二点——光照的计算。

  • 部分疑问?

计算光照的公式有很多,我们使用的是兰伯特模型,但是如何理解其中的变量值?为什么会有阴影的部分?为什么有些部分的颜色与其他部分不同?

扫描二维码关注公众号,回复: 14918153 查看本文章
  • Q:我们探讨为什么有些光亮的颜色不同:
    A:在Unity之中,假设一个平行光源带来的每条光线间隔为 d ,那么如果是正午,也就是平行光源正向下照射,那么我们可以轻易知道,此时找到物体表面的光线间隔其实也是为 d 的。但是如果光线不是正下直射,而是有点倾斜角度(清晨或黄昏时),那么如果平行光带来的光线间隔仍为 d 时,照到物体上的间距其实已经不是 d 了,因为在光线数量有限的Unity中,发生了角度的偏移。下图可以更直观的展示这个结论:
    12

  • Q:兰伯特公式是如何在Shader中工作的?
    A:在上图中,cosθ可以使用 光源方向 l 和表面法线 n点积求得。而兰伯特模型中,尾部的部分,就是 光源方向 与 表面法线 点积。结合下图可以更轻易的了解:

13
(图片取自知乎@俊铭)

根据上图,可以分为右上角的三个情况。再结合兰伯特公式,最终可以作为权重来控制颜色的输出。当点积的值小于0时,直接颜色权重变为0,(因为要控制在[ 0 , 1]的区间内)最终导致颜色为0,阴影的产生。



代码开始:

  • 逐顶点光照

1. 创建一个空场景(只有摄像机和平行光),删除该场景中的天空盒。

2. 新建一个材质,命名为Mat_Diffuse

3. 新建一个UnityShader,命名为Sha_DIffuse并将该Shader赋值给Mat_Diffuse

4. 在场景中新建一个胶囊体,将Mat_Diffuse赋予该物体。


5. 保存,开始编辑Shader代码:

①删除第3步新建的Shader里的默认代码。

②为了控制该Shader的颜色,我们需要在Properties中添加漫反射颜色

Properties{
    
    
	_Diffuse ( "Diffuse", Color) = (1,1,1,1)
}

(这里更加类似于 C# 脚本的 Public Color _Diffuse;


③在SubShader之中定义Pass语义块。在Pass的一开始就指名该Pass的光照模式:

SubShader{
    
    
	Pass{
    
    
		Tags {
    
    "LightMode"="ForwardBase"}
	}
}

只有正确定义了LightMode,我们才可以在后续得到内置的光照变量。



④声明vertex和fragment着色器 并 包含Lightint.cginc

#pragma vertex vert
#pragma fragment frag
#include "Lightint.cginc"

⑤由于在Properties中声明了_Diffuse属性,因此我们需要定义一个和该属性类型一致的变量,方便Shader控制

fixed4 _Diffuse;

(这里的就类似于 _Diffuse = new fixed4();初始化)

由于Diffuse的取值范围是 [0,1] 因此使用fixed类型。值得一提的是,这里的fixed类型其实是很常见的(在UnityShader中)

类型 精度 (CG / HLSL)
float 最高精度的浮点值。通常使用32位来存储。
half 中等精度的浮点值。通常使用16位来存储。( -60,000 ~ 60,000)
fixed 最低精度的浮点值。通常使用11位来存储。( -2.0 ~ 2.0 )

Tip:对于PC端而言,三者区别不大,因为都会被视作float的类型来处理。但是对于移动端而言,三者使用的优先级为:fixed > half > float ,且尽量少出现float。


⑥定义顶点着色器和片元着色器的输入输出结构体(着色器的输出结构体与输入结构体一致)

struct a2v {
    
    
    float4 vertex : POSITION;
    float4 normal : NORMAL;
};
struct v2f {
    
    
    float4 pos : SV_POSITION;
    fixed3 color : COLOR;
};

SV_System Value的意思,是DirectX 10 新增的系统数值语义类型。SV_POSITION会返回一个裁剪后的顶点位置信息,并直接返回到屏幕之中。如果开发PS4平台,则必须使用SV_POSITION,否则会导致细分着色器无法工作。)

⑦完善流水线中的顶点着色器

v2f vert(a2v v) {
    
    
    v2f o;
    //将定点左边从本地空间转变投影空间
    o.pos = UnityObjectToClipPos(v.vertex);
    //得到环境信息
    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

    //法线信息由物体空间转变为世界空间
    fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
    //得到光源在世界坐标中的向量(注意这里是物体指向光源)
    fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
    //漫反射计算公式:
    fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));
    o.color = ambient + diffuse;

    return o;
}

到了这一步,我们已经得到了漫反射颜色 _Diffuse 和 顶点法线 v.normal。
光的强度和颜色 以及 光的方向。光强和光色使用_LightColor0来获取。这里是因为只有一个平行光,光源方向使用_WorldSpaceLightPos0不会出错,如果在光照环境复杂的情况下可能无法得到正确的结果。

至此,漫反射公式的四个值都已经得到。在计算点积时,两个值必须实在同一个坐标空间下,这里我们将表面法线和光源方向在世界空间下进行点积运算。

⑧实现流水线中的片元着色器

Shader代码:

fixed4 frag(v2f i) : SV_Target{
    
    
	return fixed4(i.color, 1.0);
}

⑨最终结果:

3


Shader "LeonShader/shader_6_4_Diffuse"
{
    
    
    Properties{
    
    
        _Diffuse("Diffuse",Color) = (1,1,1,1)
    }
    SubShader{
    
    
        Pass{
    
    
            Tags {
    
     "LightMode" = "ForwardBase"}

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"

            fixed4 _Diffuse;

            struct a2v {
    
    
                float4 vertex : POSITION;
                float4 normal : NORMAL;
            };
            struct v2f {
    
    
                float4 pos : SV_POSITION;
                fixed3 color : COLOR;
            };

            v2f vert(a2v v) {
    
    
                v2f o;
                //将定点左边从本地空间转变投影空间
                o.pos = UnityObjectToClipPos(v.vertex);
                //得到环境信息
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                //法线信息由物体空间转变为世界空间
                fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
                //得到光源在世界坐标中的向量(注意这里是物体指向光源)
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
                //漫反射计算公式:
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));
                o.color = ambient + diffuse;

                return o;
            }

            fixed4 frag(v2f i) : SV_Target{
    
    
                return fixed4(i.color , 1.0);
            }

            ENDCG
        }
    }
    FallBack "Diffuse"
}

逐像素光照

shader代码:


Shader "LeonShader/shader_6_4_Diffuse_Pixel"
{
    
    
    Properties{
    
    
        _Diffuse("Diffuse",Color) = (1,1,1,1)
    }
        SubShader{
    
    
            Pass{
    
    
                Tags {
    
     "LightMode" = "ForwardBase"}
                
                CGPROGRAM

                #pragma vertex vert
                #pragma fragment frag
                #include "Lighting.cginc"

                fixed4 _Diffuse;

                struct a2v {
    
    
                    float4 vertex : POSITION;
                    float4 normal : NORMAL;
                };
                struct v2f {
    
    
                    float4 pos : SV_POSITION;
                    float3 worldNormal : TEXCOORD0;
                };

                v2f vert(a2v v) {
    
    
                    v2f o;
                    //将定点左边从本地空间转变投影空间
                    o.pos = UnityObjectToClipPos(v.vertex);
                    //得到环境信息
                    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                    //法线信息由物体空间转变为世界空间
                    o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);

                    return o;
                }

                fixed4 frag(v2f i) : SV_Target{
    
    
                    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                    fixed3 worldNormal = normalize(i.worldNormal);
                    fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                    fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));

                    fixed3 color = ambient + diffuse;

                    return fixed4(color , 1.0);
                }

                ENDCG
            }
    }
        FallBack "Diffuse"
}




- 思考

根据文末的效果图(没有比较逐像素法,因为逐像素法的效果非常可观),其实可以很清楚的看到,逐顶点的影子是由明显的锯齿效果的,这当然不是我们想要的。于是我在Blender制作了一个经纬度均为64的Sphere,以此来与unity默认的Sphere做对比。

如下图可以看到,出现锯齿的主要原因其实就是因为物体的细分程度不够所导致,加上了我导入的(左上角的)Sphere在Blender中添加了平滑着色处理,显得非常自然。

4

猜你喜欢

转载自blog.csdn.net/weixin_46840974/article/details/123972167