量子计算9 发表于 2022-5-9 09:28

Unity-Shader 01 光照模型

资源与声明:

1-本文章图片来源 UCSB 闫令琪 老师《GAMES101 现代计算机图形学入门》
https://sites.cs.ucsb.edu/~lingqi/teaching/games101.html
2-本文章代码来源 冯乐乐老师 《Unity Shader入门精要》
https://github.com/candycat1992/Unity_Shaders_Book/tree/unity_2017_1

<hr/>1-着色方式 Shading

1-1 平坦着色 Flat shading




逐顶点计算颜色,用三角形中心点的颜色代表整个三角形面的颜色

1-2 逐顶点着色Gouraud shading




逐顶点计算颜色,然后中间的就插值渐变过度一下;
1-3逐像素着色 Phong(冯) Shading




就是在屏幕的裁剪空间的没一点都去计算漫反射和高光颜色;
三种不同渲染方式的对比


2-漫反射光照模型实现

2-1漫反射光照模型



代码里是这样的
// 用漫反射光照公式计算
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));
能通过正比算出传递到这个点的能量,又根据夹角 算出接受了多少能量
后面的max(0,n·1)就是防止点乘=负数的情况(背面传来的)
Ld= diffusely reflected light表示的是该点反射的光(包括光强+颜色)
kd = diffuse coefficient color 表示漫反射系数(包含反射率和颜色)


I/r = energy arrived 入射光强;用光源板强度和距离定义


max(0,n·1)max是防止点乘=负数的情况(背面传来的)入射光和发现的点乘是计算cos值来判断接受面吸收了多少光;
求的是接受面的单位面积能收到的能量?
比如说 夏季 冬季,就是地球单位面积,接受到的光照就少


2-2 逐顶点光照

// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
// 这个是逐顶点光照

Shader "Unity Shaders Book/Chapter 6/Diffuse Vertex-Level" {
        Properties {
                _Diffuse ("Diffuse", Color) = (1, 1, 1, 1)// 这是漫反射的颜色 1111 表示纯白 RGB+透明度
        }
        SubShader {
                Pass {
                        Tags { "LightMode"="ForwardBase" }
               
                        CGPROGRAM
                       
                        #pragma vertex vert    //定义顶点着色器的名称
                        #pragma fragment frag//定义片元着色器的名称
                       
                        #include "Lighting.cginc" //要内置一个Unity Shader的光照库;
                       
                        fixed4 _Diffuse; //这里要声明一下;以使用Properties中定义的变量 fixed 指的是数据类型

                       
                        //Part1 Application to Vertex 结构体用于顶点着色器的输入
                        struct a2v {
                                float4 vertex : POSITION;//a2v 语义 表示顶点位置
                                float3 normal : NORMAL;    //a2v 语义 表示顶点法线
                        };
                       
                        //Part2 Vertex to Fragment 结构体,用于顶点着色器的输出,和片元着色器的输入
                        struct v2f {
                                float4 pos : SV_POSITION;//v2f 语义 表示裁剪空间的顶点坐标(projection space)
                                fixed3 color : COLOR;//v2f 语义 表示顶点颜色
                        };
                       

                        //Part3 顶点着色器 输入顶点位置+顶点法线, 返回顶点坐标+颜色
                        v2f vert(a2v v) {
                                v2f o;//定义了v2f 变量o

                                // 模型空间顶点 做个 Projection 变到裁剪空间里 一个Unity函数搞掂
                                o.pos = UnityObjectToClipPos(v.vertex);
                               
                                // 从Unity项目里得到当前的环境光信息
                                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" //如果subshader无法使用 就用默认的光照模式;
}




左 :逐顶点漫反射中:逐像素漫反射 右:半兰伯特模型
2-3逐像素光照

// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unity Shaders Book/Chapter 6/Diffuse Pixel-Level" {
        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;
                       
                        // 结构体a2v
                        struct a2v {
                                float4 vertex : POSITION;
                                float3 normal : NORMAL;
                        };
                       
                        //结构体v2f
                        struct v2f {
                                float4 pos : SV_POSITION;
                                float3 worldNormal : TEXCOORD0; //返回的是世界坐标下的法线
                        };
                       
                        //顶点着色器
                        v2f vert(a2v v) {
                                v2f o;
                                // 从三维空间到Project空间
                                o.pos = UnityObjectToClipPos(v.vertex);

                                // 顶点法线到世界坐标下的法线
                                o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);

                                return o;
                        }
                       
                        // 片元着色器 直接返回的就是Diffuse的颜色
                        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"
}

逐像素光照和逐顶点的差不太多;只是把一些语句用逐像素的方式执行了
左1是逐顶点,左二是逐像素;
逐像素要更丝滑一点;



2-4半兰伯特模型




2-2/2-3都叫做兰伯特,都会出现背部过暗
半兰伯特模型改进的是全局光的部分(多次反射)
将兰伯特的cos夹角改成cos夹角*0.5 + 0.5,而且不用做和0求max保证最暗的面也有亮度,看起来会亮一点;
代码和逐像素最大的不同就是 漫反射公式变成有个0.5系数




// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unity Shaders Book/Chapter 6/Half Lambert" {
        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;
                                float3 normal : NORMAL;
                        };
                       
                        struct v2f {
                                float4 pos : SV_POSITION;
                                float3 worldNormal : TEXCOORD0;
                        };
                       
                        v2f vert(a2v v) {
                                v2f o;
                                // Transform the vertex from object space to projection space
                                o.pos = UnityObjectToClipPos(v.vertex);
                               
                                // Transform the normal from object space to world space
                                o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
                               
                                return o;
                        }
                       
                        fixed4 frag(v2f i) : SV_Target {
                                // Get ambient term
                                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                               
                                // Get the normal in world space
                                fixed3 worldNormal = normalize(i.worldNormal);
                                // Get the light direction in world space
                                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                               
                                // 和逐片元的最大的不同就是漫反射公式
                                fixed halfLambert = dot(worldNormal, worldLightDir) * 0.5 + 0.5;
                                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;
                               
                                fixed3 color = ambient + diffuse;
                               
                                return fixed4(color, 1.0);
                        }
                       
                        ENDCG
                }
        }
        FallBack "Diffuse"
}




半兰伯特模型 要亮一点

3-高光反射模型实现

3-1高光反射模型







高光模型就是在出射角附近的视线能看到高光
上图展示的是布林-冯模型的计算方法
Ls= specularly reflected light表示的是该点反射的光(包括光强+颜色)
ks = specular coefficient color 表示漫反射系数(包含反射率和颜色)
指数p=就是去降低cos的容忍度,cos的容忍度太高了,以至于我们在45度以上都能看到很好的高光,所以就降低高光的区域,正常用的是100-200,基本3°以外就看不到高光了
I/r = energy arrived 入射光强;用光源板强度和距离定义
max(0,n·h)=其实就是表示出射光和视线的偏差值,越接近说明越大的高光、冯模型里面是直接用出射角和视线角,布林冯模型用半程向量和法线比较,差不多的;


纵轴,Ks反射率增大,模型会越来越亮,横轴,随着指数p的增大,能看见高光的区域会越来越小。

http://pic1.zhimg.com/v2-ef8a25a032b32228ad471717d17f924_r.jpg
3-2逐顶点光照

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unity Shaders Book/Chapter 6/Specular Vertex-Level" {
        Properties {
                _Diffuse ("Diffuse", Color) = (1, 1, 1, 1) //漫反射颜色
                _Specular ("Specular", Color) = (1, 1, 1, 1) //高光反射颜色
                _Gloss ("Gloss", Range(8.0, 256)) = 20 //就是公式里的指数p;用来控制高光区的大小;
        }
        SubShader {
                Pass {
                        Tags { "LightMode"="ForwardBase" }
                       
                        CGPROGRAM
                       
                        #pragma vertex vert
                        #pragma fragment frag
                       
                        #include "Lighting.cginc"
                       
                        //这样定义一下才能定义Properties里的属性

                        fixed4 _Diffuse; //输出的漫反射
                        fixed4 _Specular; //输出的高光
                        float _Gloss; //指数p
                       
                        struct a2v {
                                float4 vertex : POSITION;
                                float3 normal : NORMAL;
                        };
                       
                        struct v2f {
                                float4 pos : SV_POSITION;
                                fixed3 color : COLOR; //这个是是顶点着色器 就只要输出color
                        };
                       
                        v2f vert(a2v v) {
                                v2f o;

                                //Part1 计算漫反射

                                // 顶点从三维坐标到世界坐标
                                o.pos = UnityObjectToClipPos(v.vertex);
                               
                                // 得到全局光照
                                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                               
                                // 法线从物体坐标到世界坐标
                                fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));

                                // 光源方向也到世界坐标
                                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                               
                                // 计算漫反射
                                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
                               

                                //Part2 计算高光
                               
                                // 计算反射方向,也是一个Unity内置的函数
                                fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));

                                // 得到视线方向
                                // 漫反射是不需要考虑视线方向的,但是高光需要
                                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);
                               
                                // 计算高光的量
                                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
                               
                                //总的color = 环境光+高光+漫反射
                                o.color = ambient + diffuse + specular;
                                                              
                                return o;
                        }
                       
                        //片元着色器就直接输出color就好了
                        fixed4 frag(v2f i) : SV_Target {
                                return fixed4(i.color, 1.0);
                        }
                       
                        ENDCG
                }
        }
        FallBack "Specular"
}

我们还能调节gloss
来控制高光的区域


3-3逐像素光照





可能看起来会更柔和一点

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unity Shaders Book/Chapter 6/Specular Pixel-Level" {
        Properties {
                _Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
                _Specular ("Specular", Color) = (1, 1, 1, 1)
                _Gloss ("Gloss", Range(8.0, 256)) = 20
        }
        SubShader {
                Pass {
                        Tags { "LightMode"="ForwardBase" }
               
                        CGPROGRAM
                       
                        #pragma vertex vert
                        #pragma fragment frag

                        #include "Lighting.cginc"
                       
                        fixed4 _Diffuse;
                        fixed4 _Specular;
                        float _Gloss;
                       
                        struct a2v {
                                float4 vertex : POSITION;
                                float3 normal : NORMAL;
                        };
                       
                        struct v2f {
                                float4 pos : SV_POSITION; //裁剪空间中的顶点坐标
                                float3 worldNormal : TEXCOORD0; //世界坐标下的法线方向
                                float3 worldPos : TEXCOORD1; //世界坐标下的顶点坐标
                        };
                       
                        v2f vert(a2v v) {
                                v2f o;
                                // Transform the vertex from object space to projection space
                                o.pos = UnityObjectToClipPos(v.vertex);
                               
                                // Transform the normal from object space to world space
                                o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
                                // Transform the vertex from object spacet to world space
                                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                               
                                return o;
                        }
                       
                        //片元着色器
                        //和顶点着色器是完全一样的计算过程
                        fixed4 frag(v2f i) : SV_Target {
                                // Get ambient term
                                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                               
                                fixed3 worldNormal = normalize(i.worldNormal);
                                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                               
                                // 漫反射
                                // Compute diffuse term
                                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
                               
                                // 高光反射
                                // Get the reflect direction in world space
                                fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
                                // Get the view direction in world space
                                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
                                // Compute specular term
                                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
                               
                                return fixed4(ambient + diffuse + specular, 1.0);
                        }
                       
                        ENDCG
                }
        }
        FallBack "Specular"
}

3-4布林-冯模型

我们要记得 对比一下;
不管冯模型 还是布林冯模型,求的都是出射角与视线角的差值;
但是冯模型直接求的是初射角和视线角,布林冯模型是法线和半程向量;



冯模型



布林冯模型

布林-冯 会看起来更大更亮一点,所以实际中我们一般都选用布林冯光照模型;
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unity Shaders Book/Chapter 6/Blinn-Phong" {
        Properties {
                _Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
                _Specular ("Specular", Color) = (1, 1, 1, 1)
                _Gloss ("Gloss", Range(8.0, 256)) = 20
        }
        SubShader {
                Pass {
                        Tags { "LightMode"="ForwardBase" }
               
                        CGPROGRAM
                       
                        #pragma vertex vert
                        #pragma fragment frag
                       
                        #include "Lighting.cginc"
                       
                        fixed4 _Diffuse;
                        fixed4 _Specular;
                        float _Gloss;
                       
                        struct a2v {
                                float4 vertex : POSITION;
                                float3 normal : NORMAL;
                        };
                       
                        struct v2f {
                                float4 pos : SV_POSITION;
                                float3 worldNormal : TEXCOORD0;
                                float3 worldPos : TEXCOORD1;
                        };
                       
                        v2f vert(a2v v) {
                                v2f o;
                                // Transform the vertex from object space to projection space
                                o.pos = UnityObjectToClipPos(v.vertex);
                               
                                // Transform the normal from object space to world space
                                o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
                               
                                // Transform the vertex from object spacet to world space
                                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                               
                                return o;
                        }
                       
                        fixed4 frag(v2f i) : SV_Target {
                                // Get ambient term
                                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                               
                                fixed3 worldNormal = normalize(i.worldNormal);
                                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                               
                                // Compute diffuse term
                                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
                               
                                // Get the view direction in world space
                                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
                                // Get the half direction in world space
                                fixed3 halfDir = normalize(worldLightDir + viewDir);
                                // Compute specular term
                                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
                               
                                return fixed4(ambient + diffuse + specular, 1.0);
                        }
                       
                        ENDCG
                }
        }
        FallBack "Specular"
}


Thanks!
页: [1]
查看完整版本: Unity-Shader 01 光照模型