Shaderlab Notizen 12 漫反射可编程Shader

一、Diffuse Reflection

漫反射(Diffuse Reflection),又称Lambert反射,是投射在粗糙表面上的光向各个方向反射的现象。当一束平行的入射光线射到粗糙的表面时,表面会把光线向着四面八方反射,所以入射线虽然互相平行,由于各点的法线方向不一致,造成反射光线向不同的方向无规则地反射。

在生活中,我们看到的月球的光近乎完全是漫反射。粉笔或者磨砂纸也是漫反射。事实上,任何表面的漫反射看起来都是类似于磨砂表面的效果。

而在完美的漫反射的情况下,所观察到的反射光的强度取决于在表面法线矢量和入射光的光线之间的角度的余弦。如图所示,经过归一化的物体表面法线向量N与物体表面正交,光源照射到物体表面的光线方向为L。

8CAC82DC-44B0-4DA5-B7B1-31D41A911ADD.png

也就是说,我们可以使用表面的法线矢量N和入射光方向矢量L,计算漫反射。

想要计算出眼睛观察到的漫反射光Idiffuse,需要计算归一化的表面法向量N和归一化的方向光源向量L之间夹角的余弦值,即点积运算N·L。因为任何两个向量a和b的点积运算可以表示为:

Paste_Image.png

而对于已经经过归一化的向量,|a| 和|b|都是1。

如果点积运算N·L为负,那么光源方向就是在表面内部照射过来的,这是错误的。这种情况下,我们就将反射光设为0即可。这可以通过代码max(0, N·L)来实现,这样就确保了在点积结果为负时,得到的结果为0。此外,漫反射光还取决于入射光light Iincoming和材质的漫反射系数Kdiffuse。而对于一个黑色的表面,材质漫反射系数 为0,而对于白色的表面,材质的漫反射强度Kdiffuse就为1.

根据如上的表述,漫反射强度的方程如下:

Paste_Image.png

ps:此公式适用于任何单一的颜色分量(如红、绿、蓝),也适用于颜色分量混合颜色的入射光。

二、实现

  • 核心代码可以在顶点着色器中实现,也可以在片段着色函数中实现。

  • 需在世界空间中进行实现,因为世界空间中Unity提供了光源方向。

  • 关于如何获取参数,总结如下:

  • Kdiffuse通过属性properties中指定后传进来。

  • 世界空间的光源方向由unity内置变量_WorldSpaceLightPos0给出。

  • 光源颜色Iincoming由unity内置变量_LightColor0给出。

  • 环境光颜色通过内置变量UNITY_LIGHTMODEL_AMBIENT给出。

  • 用Tags {"LightMode" = "ForwardBase"}确保上述内置变量的值处于正确的状态。

  • 世界空间下的物体表面的法线向量,可以通过输出参数语义NORMAL来获得物体空间下的表面的法线向量,然后将此向量从物体空间转到世界空间中获得。

2.1 单色可调的漫反射光照Shader
源码如下:

Shader "Shader/Diffuse(Lambert) Shader"
{
    //------------------------------------【属性值】------------------------------------
    Properties
    {
        //颜色值
        _Color("Main Color", Color) = (1, 1, 1, 1)

    }

    //------------------------------------【唯一的子着色器】------------------------------------
    SubShader
        {
            //渲染类型设置:不透明
            Tags{ "RenderType" = "Opaque" }
            //设置光照模式:ForwardBase
            Tags{ "LightingMode" = "ForwardBase" }
            //细节层次设为:200
            LOD 200

            //--------------------------------唯一的通道-------------------------------
            Pass
            {
                //===========开启CG着色器语言编写模块===========
                CGPROGRAM

                //编译指令:告知编译器顶点和片段着色函数的名称
                #pragma vertex vert
                #pragma fragment frag

                //包含头文件
                #include "UnityCG.cginc"

                //顶点着色器输入结构
                struct appdata
                {
                    float4 vertex : POSITION;//顶点位置
                    float3 normal : NORMAL;//法线向量坐标
                };

                //顶点着色器输出结构
                struct v2f
                {
                    float4 position : SV_POSITION;//像素位置
                    float3 normal : NORMAL;//法线向量坐标
                };

                //变量声明
                float4 _LightColor0;
                float4 _Color;

                //--------------------------------【顶点着色函数】-----------------------------
                // 输入:顶点输入结构体
                // 输出:顶点输出结构体
                //---------------------------------------------------------------------------------
                v2f vert(appdata input)
                {
                    //【1】声明一个输出结构对象
                    v2f output;

                    //【2】填充此输出结构
                    //输出的顶点位置为模型视图投影矩阵乘以顶点位置,也就是将三维空间中的坐标投影到了二维窗口
                    output.position = mul(UNITY_MATRIX_MVP, input.vertex);
                    //获取顶点在世界空间中的法线向量坐标
                    output.normal = mul(float4(input.normal, 0.0), _World2Object).xyz;

                    //【3】返回此输出结构对象
                    return output;
                }

                //--------------------------------【片段着色函数】-----------------------------
                // 输入:顶点输出结构体
                // 输出:float4型的像素颜色值
                //---------------------------------------------------------------------------------
                fixed4 frag(v2f input) : COLOR
                {
                    //【1】先准备好需要的参数
                    //获取法线的方向
                    float3 normalDirection = normalize(input.normal);
                    //获取入射光线的值与方向
                    float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz);

                    //【2】计算出漫反射颜色值  Diffuse=LightColor * MainColor * max(0,dot(N,L))
                    float3 diffuse = _LightColor0.rgb * _Color.rgb * max(0.0, dot(normalDirection, lightDirection));

                    //【3】合并漫反射颜色值与环境光颜色值
                    float4 DiffuseAmbient = float4(diffuse, 1.0) + UNITY_LIGHTMODEL_AMBIENT;

                    //【4】将漫反射-环境光颜色值乘上纹理颜色,并返回
                    return DiffuseAmbient;
                }

                //===========结束CG着色器语言编写模块===========
                ENDCG

            }
        }
}

2.2 可调颜色和自定义纹理的漫反射光照Shader

在Properties属性中加上一项纹理,然后在最终的漫反射颜色计算完成之后,乘上纹理即可。
源码如下:

Shader "Shader/Diffuse(Lambert) Shader with Texture"
{
    //------------------------------------【属性值】------------------------------------
    Properties
    {
        //主纹理
        _MainTex("Texture", 2D) = "white"{}
        //主颜色值
        _Color("Main Color", Color) = (1, 1, 1, 1)

    }

    //------------------------------------【唯一的子着色器】------------------------------------
    SubShader
        {
            //渲染类型设置:不透明
            Tags{ "RenderType" = "Opaque" }
            //设置光照模式:ForwardBase
            Tags{ "LightingMode" = "ForwardBase" }
            //细节层次设为:200
            LOD 200

            //--------------------------------唯一的通道-------------------------------
            Pass
            {
                //===========开启CG着色器语言编写模块===========
                CGPROGRAM

                //编译指令:告知编译器顶点和片段着色函数的名称
                #pragma vertex vert
                #pragma fragment frag

                //包含头文件
                #include "UnityCG.cginc"

                //顶点着色器输入结构
                struct appdata
                {
                    float4 vertex : POSITION;//顶点位置
                    float3 normal : NORMAL;//法线向量坐标
                    float2 texcoord : TEXCOORD0;//纹理坐标
                };

                //顶点着色器输出结构
                struct v2f
                {
                    float4 positon : SV_POSITION;//像素位置
                    float3 normal : NORMAL;//法线向量坐标
                    float2 texcoord : TEXCOORD0;//纹理坐标
                };

                //变量声明
                float4 _LightColor0;
                float4 _Color;
                sampler2D _MainTex;

                //--------------------------------【顶点着色函数】-----------------------------
                // 输入:顶点输入结构体
                // 输出:顶点输出结构体
                //---------------------------------------------------------------------------------
                v2f vert(appdata input)
                {
                    //【1】声明一个输出结构对象
                    v2f output;

                    //【2】填充此输出结构
                    //输出的顶点位置为模型视图投影矩阵乘以顶点位置,也就是将三维空间中的坐标投影到了二维窗口
                    output.positon = mul(UNITY_MATRIX_MVP, input.vertex);
                    //获取顶点在世界空间中的法线向量坐标
                    output.normal = mul(float4(input.normal, 0.0), _World2Object).xyz;
                    //输出的纹理坐标也就是输入的纹理坐标
                    output.texcoord = input.texcoord;

                    //【3】返回此输出结构对象
                    return output;
                }

                //--------------------------------【片段着色函数】-----------------------------
                // 输入:顶点输出结构体
                // 输出:float4型的像素颜色值
                //---------------------------------------------------------------------------------
                fixed4 frag(v2f input) : COLOR
                {
                    //【1】先准备好需要的参数
                    //获取纹理颜色
                    float4 texColor = tex2D(_MainTex, input.texcoord);
                    //获取法线的方向
                    float3 normalDirection = normalize(input.normal);
                    //获取入射光线的值与方向
                    float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz);

                    //【2】计算出漫反射颜色值  Diffuse=LightColor * MainColor * max(0,dot(N,L))
                    float3 diffuse = _LightColor0.rgb * _Color.rgb * max(0.0, dot(normalDirection, lightDirection));

                    //【3】合并漫反射颜色值与环境光颜色值
                    float4 DiffuseAmbient = float4(diffuse, 1.0) + UNITY_LIGHTMODEL_AMBIENT;

                    //【4】将漫反射-环境光颜色值乘上纹理颜色,并返回
                    return DiffuseAmbient* texColor;
                }

                //===========结束CG着色器语言编写模块===========
                ENDCG
            }
        }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容