• 【图形学】17 光照模型(二、漫反射的Shader实现)


    来源:《UNITY SHADER入门精要》

    1、环境光和自发光

      环境光可以在Window->Redenering->Lighting->Enviroment->Intensity Multipler这个地方更改。
      在 ShaderLab 中,可以通过内置的变量 UNITY_LIGHTMODEL_AMBIENT 来获取环境光的颜色和强度信息。

    2、基本的逐顶点光照代码实现

    Shader "Unity Shaders Book/Chapter 6/Diffuse Vertex-Level" {
    	Properties {
    		_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
    	}
    
    • 1
    • 2
    • 3
    • 4

      首先,我们写出我们的 Shader 名字,然后再 Properties 语义块中声明一个 Color 类型的属性,并设置其初始值为白色。

    	SubShader {
    		Pass { 
    			Tags { "LightMode"="ForwardBase" }
    		
    			CGPROGRAM
    			
    			#pragma vertex vert
    			#pragma fragment frag
    			
    			#include "Lighting.cginc"
    			
    			fixed4 _Diffuse;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

      SubShader中声明Pass,Pass中指令该Pass的光照模式为 ForwardBase。这个值定义了该 Pass 在光照流水线中的角色。为了获得我们需要进行计算的光照,我们还需要包含内置文件“Lighting.cginc”。还有,声明同名的 _Diffuse 可以获得和该属性类型相匹配的变量。这样,我们接下来就能用到漫反射的材质属性了。

    			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,使用的是Unity内的宏定义
    				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 worldLight = normalize(_WorldSpaceLightPos0.xyz);
    				// Compute diffuse term
    				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb *                                                        (dot(worldNormal, worldLight));
    				
    				o.color = ambient + diffuse;
    				
    				return o;
    			}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

      在第2行,我们定义了要输出的结构体。
      第4行,我们将定点坐标从模型空间转换到投影空间。我们如上,可以直接使用 Unity 内置的函数:UnityObjectToClipPos(v.vertex) 将坐标转换到裁剪空间(投影空间)之外。我们还可以使用 o.pos = mul(NUITY_MATRIX_MVP, v.vertex); 手动乘法来得到裁剪空间的位置。
      第7行,我们通过内置宏 UNITY_LIGHTMODEL_AMBIENT 获得环境光。
      第10行,我们通过模型到世界的矩阵 UNITY_MATRIX_M(Unity中也可以写作 unity_ObjectToWorld),将法线也转换到世界空间。但是注意,我们这里应该使用左乘的方法用 unity_WorldToObject,来避免 unity 中的隐形求逆。
      第14行,我们采用的是Unity给我们提供的内置变量 _LightColor0 来访问该 Pass 处理的光源的颜色和强度信息。这里,因为我们假设场景中只有一个光源且是平行光,所以使用了 _WorldSpaceLightPos0 来获得光源的方向。
      记得到我们的 漫反射光照 **兰伯特定律(Lambet’s law)**公式:
    c d i f f u s e = ( c l i g h t ⋅ m d i f f u s e ) max ⁡ ( 0 , n    ⋅    I ) \boldsymbol{c}_{diffuse}=\left( \boldsymbol{c}_{light}\cdot \boldsymbol{m}_{diffuse} \right) \max \left( 0, n\,\,\cdot \,\,I \right) cdiffuse=(clightmdiffuse)max(0,nI)
      最后,相加环境光得到最后的结果。

    			fixed4 frag(v2f i) : SV_Target {
    				return fixed4(i.color, 1.0);
    			}
    			ENDCG
    		}
    	}
    	FallBack "Diffuse"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

      片元着色器什么也不用做,直接返回原来的颜色。

    3、基本逐像素光照的实现

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

      前面的内容与之前的保持一致并且不变。

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

      相比上一段代码,这里使用了不同的语义,worldNormal的语义是采用的纹理坐标的语义。

    			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);
    				
    				// Compute diffuse term
    				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
    				
    				fixed3 color = ambient + diffuse;
    				
    				return fixed4(color, 1.0);
    			}
    			
    			ENDCG
    		}
    	} 
    	FallBack "Diffuse"
    }
    
    • 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
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    4、半兰伯特光照模型的实现

    				// Compute diffuse term
    				fixed halfLambert = dot(worldNormal, worldLightDir) * 0.5 + 0.5;
    				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;
    
    • 1
    • 2
    • 3

      单纯的把上面的 Compute diffuse term 的部分改成那个公式上的部分就好了。

    5、新手实现的注意事项

      Unity和OpenGL一样,使用的是列向量,所以是矩阵 X 向量。顺序是从左到右。而DirectX中,这点是不一样的,它是行向量,是通过 向量 X 矩阵 来进行运算的。

  • 相关阅读:
    视频集中存储/云存储EasyCVR启动后查询端口是否被占用出错,该如何解决?
    Flutter 混合开发调试
    ps丢失d3dcompiler_47.dll怎么办,这四个方法都能解决
    AWS 高管外流,竟是 MongoDB “撬墙角”?
    WGCLOUD的web ssh提示websocket服务连接已断开
    【计算机网络】什么是http?
    shell脚本入门
    邮件网关&CAC2.0防御并行:提升高校师生邮箱账号的全面安全
    基于Yolov8的工业小目标缺陷检测(3):多检测头提升小目标检测精度
    越狱(快速幂C++)
  • 原文地址:https://blog.csdn.net/qq_40891541/article/details/126157688