• 【图形学】18 光照模型(三、镜面反射的Shader实现)


    来源:《UNITY SHADER入门精要》

    1、镜面反射ShaderLab的方式

      我们回忆之前的公式:
    c s p c u l a r = ( c l i g h t ⋅ m s p e c u l a r ) max ⁡ ( 0 , v ^    ⋅    r ) m g l o s s \boldsymbol{c}_{spcular}=\left( \boldsymbol{c}_{light}\cdot \boldsymbol{m}_{specular} \right) \max \left( 0, \hat{v}\,\,\cdot \,\,\boldsymbol{r} \right) ^{m_{gloss}} cspcular=(clightmspecular)max(0,v^r)mgloss
      我们计算镜面反射需要四个参数:入射光线 c l i g h t \boldsymbol{c}_{light} clight 、材质的高光反射系数 m s p e c u l a r \boldsymbol{m}_{specular} mspecular 、视角方向 v ^ \hat{v} v^ 、反射方向 r \boldsymbol{r} r 。而其中,反射方向 r \boldsymbol{r} r 又需要由表面法线 n ^ \hat{n} n^ 和 光源方向 I \boldsymbol{I} I 计算而得。

      公式不难写出,不过,CG提供了计算反射方向的函数:reflect(i, n)。其中参数:i 为入射方向;n 为法线方向。

    2、逐顶点的镜面反射

    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
    	}
    		SubShader {
    		Pass { 
    			Tags { "LightMode"="ForwardBase" }
    			
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

      这里定义了三个 Properties ,其中,_Diffuse 用来控制漫反射的颜色系数, _Specular 用来控制高光反射的颜色, _Gloss 用来控制高光区域的大小。
      这里的 LightMode 设置为 ForwardBase 定义了该 Pass 在 Unity 的光照流水线中的角色。

    			CGPROGRAM
    			
    			#pragma vertex vert
    			#pragma fragment frag
    			
    			#include "Lighting.cginc"
    			
    			fixed4 _Diffuse;
    			fixed4 _Specular;
    			float _Gloss;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

      同样,在CGPROGRAM之后,我们包含头文件"Lighting.cginc",用 #pragma 指定顶点/片元着色器的函数。
      声明 Properties 语义块中的属性:由于颜色属性的范围在0到1之间,因此对于Diffuse和Specular属性我们可以使用fixed精度的变量来存储它。而Gloss的范围很大,因此我们使用flot精度来存储。

    			struct a2v {
    				float4 vertex : POSITION;
    				float3 normal : NORMAL;
    			};
    			
    			struct v2f {
    				float4 pos : SV_POSITION;
    				fixed3 color : COLOR;
    			};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

      定义输入和输出要使用的结构体

    			v2f vert(a2v v) {
    				v2f o;
    				// Transform the vertex from object space to projection space
    				o.pos = UnityObjectToClipPos(v.vertex);
    				
    				// Get ambient term
    				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
    				
    				// Transform the normal from object space to world space
    				fixed3 worldNormal = normalize(mul((float3x3)unity_ObjectToWorld, v.normal));
    				// Get the light direction in world space
    				fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
    				
    				// Compute diffuse term
    				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

      前面的计算是关于漫反射的,跟之前是一致的。

    				// 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 - mul(unity_ObjectToWorld, v.vertex).xyz);
    				
    				// Compute specular term
    				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
    				
    				o.color = ambient + diffuse + specular;
    							 	
    				return o;
    			}
    			
    			fixed4 frag(v2f i) : SV_Target {
    				return fixed4(i.color, 1.0);
    			}
    			
    			ENDCG
    		}
    	} 
    	FallBack "Specular"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

      这里采用的 CG 的 reflect() 函数计算了反射光线。由于参数要求传入的方向是从光源到交点处的,所以,采用的是它的反向量。
      然后,我们用计算得到的反射向量,计算得到高光值,然后都相加起来,返回就好了。
      最后,第21行,我们磨人的FallBack设置为 内置的 Specular。

      最后,我们注意到,逐顶点光照,由于线性插值的缘故,会有明细的线性效果,不够真实。所以,我们需要逐像素来出来计算。在这里插入图片描述

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oIf2liTz-1659590622745)(assets/image-20220620162252379.png)]

    3、逐像素光照计算

      

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

      前面的代码和之前没有区别

    			struct a2v {
    				float4 vertex : POSITION;
    				float3 normal : NORMAL;
    			};
    			
    			struct v2f {
    				float4 pos : SV_POSITION;
    				float3 worldNormal : TEXCOORD0;
    				float3 worldPos : TEXCOORD1;
    			};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

      因为是逐像素进行计算。这里的输出皆使用纹理坐标的语义来接收。

    			v2f vert(a2v v) {
    				v2f o;
    				// Transform the vertex from object space to projection space
    				o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
    				
    				// Transform the normal from object space to world space
    				o.worldNormal = mul(v.normal, (float3x3)_World2Object);
    				// Transform the vertex from object spacet to world space
    				o.worldPos = mul(_Object2World, v.vertex).xyz;
    				
    				return o;
    			}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

      顶点着色器所做的事情就是计算 世界空间下的法线方向和顶点坐标,并把它们传递给片元着色器。

    			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"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
  • 相关阅读:
    针对舆情分析近五年参考文献的分析报告
    中国贵金属白银技巧:与做人
    接口测试实战 | Android 高版本无法抓取 HTTPS,怎么办?
    数据结构-------队列
    虚拟存储器
    【Ray】ray.remote和option
    嵌入式Linux上ifpulgd的使用配置与qemu模拟验证
    创建和销毁对象:用静态工厂方法代替构造器
    阿里云国际版CDN-阿里云CDN是什么?阿里云折扣怎么买
    任务调度xxljob的使用记录
  • 原文地址:https://blog.csdn.net/qq_40891541/article/details/126157700