UnityShader-基础光照模型

标准光照模型


  • 自发光: 描述给定一个方向时,模型表面会向这个方向发射多少辐射量
  • 高光反射: 描述光线从光源照到物体表面时,会向完全镜面反射方向发射多少辐射量
  • 漫反射: 描述光线从光源照到物体表面时,会向每个方向发射多少辐射量
  • 环境光(ambient): 描述所有其他的间接光照

环境光与自发光


这两种光照不需要经过计算,用常量表示就可以了。

环境光: 可以在Window->Rendering->LightingSettings->EnvironmentLighting中设置。在Shader计算中通过Unity的内置变量UNITY_LIGHTMODEL_AMBIENT得到环境光的颜色和强度信息。

自发光: 不需要计算或者设置属性。一般的物体都没有自发光属性,如果需要的话,在片元着色器输出的时候加上自定义的自发光颜色就好了。

漫反射


兰伯特光照模型: 当光源向物体表面照射时,会向四面八方反射,这个现象称为漫反射。漫发射的光照强度符合Lambert定律,即反射光强与该点的光源方向和法线的夹角余弦值(两向量点乘)成正比。这个其实根据生活中的现象思考一下就知道了,一束光垂直照射下来(夹角为0度),反射的光强最强,水平面方向(夹角90度)照射过来,光强最弱。

而为了防止光源方向和法线方向的余弦值为负值,做了个取最大值的操作来将余弦值控制最小为0,防止物体被后面的光源照亮。

兰伯特公式(Lambert):
兰伯特公式

漫反射Diffuse颜色 = 直射光颜色 * max(0, cos(光源方向和法线方向夹角)) * 材质自身色彩

半兰伯特光照模型: 兰伯特光照模型存在一个问题:光照射不到的地方模型表面是黑色的。为了改善这个问题,特地提出了半兰伯特光照模型,在光源方向和法线点乘结果上进行偏移,将其结果[-1,1]映射到[0,1]范围内,这样即使在背光处,模型表面也有了明暗变化。值得注意的是,半兰伯特模型没有任何物理依据,只是一个视觉加强的技术。

半兰伯特公式(HalfLambert):
半兰伯特公式

漫反射Diffuse颜色 = 直射光颜色 * (cos(光源方向和法线方向夹角)*0.5 + 0.5) * 材质自身色彩

普通Lambert光照模型和HalfLambert光照模型区别:

HalfLambert和普通光照模型
兰伯特和半兰伯特模型区别

逐顶点计算和逐像素计算区别: UnityShader中可以在顶点着色器或者片元着色器中计算漫反射光照,逐顶点计算是通过顶点插值来计算多边形覆盖区域的像素颜色,所以逐顶点计算的光照在背光面和向光面交界处会有一些锯齿,视觉效果更粗糙(低模粗糙更明显),使用逐像素计算光照效果更平滑,视觉效果更好。

逐顶点计算:

Shader "cc/shader1" {
    Properties {
        _Diffuse ("Diffuse",Color) = (1,1,1,1)
    }
    SubShader {
        Pass {
            Tags {"LightMode" = "ForwardBase"// 光照流水线,定义了才能得到一些内置光照变量
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"                       // 后面用到 _LightColor0
            fixed4 _Diffuse;                                // 声明属性变量
            struct a2vfloat4 vertex : POSITION;
                float3 normal : NORMAL;;
            struct v2ffloat4 pos : SV_POSITION;
                fixed3 color : COLOR0;;
            v2f vert(a2v v)v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;                          // 获得环境光,Unity内置变量
                // 等价 UnityObjectToWorldNormal(v.normal)
                fixed3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));    // 将法线从模型空间转到世界空间
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);                // 光源方向(假设场景只有一个光源且为平行光)
                // _LightColor0:光源颜色和强度。saturate:把参数限制在[0,1],CG提供的函数
                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 "cc/shader2" {
    Properties {
        _Diffuse ("Diffuse",Color) = (1,1,1,1)
    }
    SubShader {
        Pass {
            Tags {"LightMode" = "ForwardBase"// 光照流水线,定义了才能得到一些内置光照变量
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"                       // 后面用到 _LightColor0
            fixed4 _Diffuse;                                // 声明属性变量
            struct a2vfloat4 vertex : POSITION;
                float3 normal : NORMAL;;
            struct v2ffloat4 pos : SV_POSITION;
                fixed3 worldNormal : TEXCOORD0;             // 获取纹理坐标;
            v2f vert(a2v v)v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                // 不需要计算光照模型,直接把世界空间法线传给着色器
                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 worldLight = normalize(_WorldSpaceLightPos0.xyz);        // 光源方向
                // _LightColor0:光源颜色和强度,saturate:把参数限制在[0,1]
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLight));
                fixed3 color = ambient + diffuse;
                return fixed4(color,1.0);
            }
            ENDCG
        }
    }
    FallBack "Diffuse"

半兰伯特光照模型:

Shader "cc/shader2" {
    Properties {
        _Diffuse ("Diffuse",Color) = (1,1,1,1)
    }
    SubShader {
        Pass {
            Tags {"LightMode" = "ForwardBase"// 光照流水线,定义了才能得到一些内置光照变量
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"                       // 后面用到 _LightColor0
            fixed4 _Diffuse;                                // 声明属性变量
            struct a2vfloat4 vertex : POSITION;
                float3 normal : NORMAL;;
            struct v2ffloat4 pos : SV_POSITION;
                fixed3 worldNormal : TEXCOORD0;             // 获取纹理坐标;
            v2f vert(a2v v)v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                // 不需要计算光照模型,直接把世界空间法线传给着色器
                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 worldLight = normalize(_WorldSpaceLightPos0.xyz);        // 光源方向
                // _LightColor0:光源颜色和强度
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * (dot(worldNormal,worldLight)*0.5 + 0.5);
                fixed3 color = ambient + diffuse;
                return fixed4(color,1.0);
            }
            ENDCG
        }
    }
    FallBack "Diffuse"

高光反射


高光反射指的是光源照到物体表面时,会向该点完全镜面进行反射。计算高光反射需要知道四个参数,光的颜色、光的强度、材质的高光反射系数(_Specular)、视角方向(v)和反射方向(r),其中反射方向r可以通过计算得到。

高光反射公式:
在这里插入图片描述

高光反射光照值 = (光的颜色和强度 * 高光反射系数) * pow(saturate(dot(视角方向 ,反射方向)), 高光范围系数);

逐顶点计算:

Shader "cc/shader3" {
    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"                       // 后面用到 _LightColor0
            fixed4 _Diffuse;
            fixed4 _Specular;
            fixed _Gloss;

            struct a2vfloat4 vertex : POSITION;
			    float3 normal : NORMAL;;
			struct v2ffloat4 pos : SV_POSITION;
			    fixed3 color : COLOR0;;
			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 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
			    fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLightDir));
			    // 计算获得出射方向r。reflect:发射方向,CG提供的函数。worldLightDir是光照方向,与L方向相反。
			    fixed reflectDic = normalize(reflect(-worldLightDir,worldNormal));
			    // 视觉方向v。即相机到物体的向量归一化。
			    // 等价UnityWorldSpaceViewDir(mul(unity_ObjectToWorld,v.vertex))
			    fixed viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld,v.vertex).xyz);
			    // 高光反射系数,Phong模型公式,假设只有一个平行光源_LightColor0。
			    fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDic,viewDir)),_Gloss);
			    o.color = ambient + diffuse + specular;
			    return o;fixed4 frag(v2f i) : SV_Target {
			    return fixed4(i.color,1.0);
			}
            ENDCG
        }
    }
    FallBack "Specular"

逐像素计算:

Shader "cc/shader4" {
    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"                       // 后面用到 _LightColor0
            fixed4 _Diffuse;
            fixed4 _Specular;
            fixed _Gloss;

            struct a2vfloat4 vertex : POSITION;
			    float3 normal : NORMAL;;
			struct v2ffloat4 pos : SV_POSITION;
			    float3 worldNormal : TEXCOORD0;
			    float3 worldPos : TEXCOORD1;;
			v2f vert (a2v v)v2f o;
			    o.pos = UnityObjectToClipPos(v.vertex);
			    o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);    
			    o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
			    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));
			    fixed reflectDic = normalize(reflect(-worldLightDir,worldNormal));
			    fixed viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
			    fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDic,viewDir)),_Gloss);
			    return fixed4(ambient + diffuse + specular,1.0);
			}
            ENDCG
        }
    }
    FallBack "Specular"

Phong和Blinn-Phong光照模型


以上介绍了环境光、漫反射、高光反射三种光照的计算方式,三种光照结合在一起称为Phone光照模型。如果在顶点着色器中实现Phong光照模型的,叫做Gouraud着色(Gouraud Shading);如果在片段着色器中实现Phong光照模型的,叫做Phong着色(Phong Shading)。

由于Phone光照模型需要计算光照的反射方向向量,这个是比较耗时的,因此砖家们又提出了一种Blinn-Phong光照模型。Blinn-Phong光照模型没有使用反射方向,而是引入了一个新的矢量h,它是通过视角方向v和光照方向I相加后再归一化得到的。即:
在这里插入图片描述
Blinn-Phong光照模型公式如下:
在这里插入图片描述

高光反射光照值 = (光的颜色和强度 * 高光反射系数) * pow(saturate(dot(法线向量 ,引入的矢量h)), 高光范围系数);

在相同条件下Blinn-Phong的高光范围要比Phong更大,写实效果Phong光照模型更好。但算法简单,运行速度快是Blinn-Phong光照模型的优点。

Blinn-Phong光照模型代码如下:

Shader "cc/shader4" {
    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"                       // 后面用到 _LightColor0
            fixed4 _Diffuse;
            fixed4 _Specular;
            fixed _Gloss;

            struct a2vfloat4 vertex : POSITION;
			    float3 normal : NORMAL;;
			struct v2ffloat4 pos : SV_POSITION;
			    float3 worldNormal : TEXCOORD0;
			    float3 worldPos : TEXCOORD1;;
			v2f vert (a2v v)v2f o;
			    o.pos = UnityObjectToClipPos(v.vertex);
			    o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);    
			    o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
			    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));
			    // 视觉方向v
				//fixed viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
				fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
				// 单位向量h
				fixed3 halfDir = normalize(worldLightDir + viewDir);
				// 套公式
				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal,halfDir)),_Gloss);
			    return fixed4(ambient + diffuse + specular,1.0);
			}
            ENDCG
        }
    }
    FallBack "Specular"

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 下面是一个简单的Unity基础光照模型Shader的代码: ``` Shader "Custom/BasicLighting" { Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("Albedo (RGB)", 2D) = "white" {} _Metallic ("Metallic", Range(0,1)) = 0.0 _Smoothness ("Smoothness", Range(0,1)) = 0.5 _Emission ("Emission", 2D) = "white" {} } SubShader { Tags {"Queue"="Geometry" "RenderType"="Opaque"} LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float3 normal : TEXCOORD1; float3 viewDir : TEXCOORD2; float3 worldPos : TEXCOORD3; float4 pos : SV_POSITION; }; sampler2D _MainTex; sampler2D _Emission; float4 _Color; float _Metallic; float _Smoothness; v2f vert (appdata v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = v.uv; o.normal = UnityObjectToWorldNormal(v.normal); o.viewDir = normalize(_WorldSpaceCameraPos - v.vertex.xyz); o.worldPos = v.vertex.xyz; return o; } float4 frag (v2f i) : SV_Target { float4 col = _Color * texture(_MainTex, i.uv); float3 diffuse = col.rgb * max(0, dot(i.normal, -_WorldSpaceLightPos0.xyz)); float3 specular = pow(max(0, dot(reflect(i.viewDir, i.normal), -_WorldSpaceLightPos0.xyz)), _Gloss); float4 emission = texture(_Emission, i.uv); float4 finalColor = col * (diffuse + specular) + emission; finalColor.a = col.a; return finalColor; } ENDCG } } FallBack "Diffuse" } ``` 这是一个使用简单的基础光照模型,其中包含颜色,主纹理,金属感,光滑度和发射量等 ### 回答2: Unity基础光照模型shader可以通过在顶点和片段着色器中进行编写来实现。以下是一个简单的Unity基础光照模型shader的示例: 顶点着色器: ``` Shader "Custom/BasicLightingShader" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags {"Queue"="Transparent" "RenderType"="Opaque"} LOD 200 CGPROGRAM #pragma surface surf Lambert sampler2D _MainTex; struct Input { float2 uv_MainTex; }; void surf (Input IN, inout SurfaceOutput o) { o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb; o.Alpha = tex2D (_MainTex, IN.uv_MainTex).a; } ENDCG } } ``` 片段着色器: ``` Shader "Custom/BasicLightingShader" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags {"Queue"="Transparent" "RenderType"="Opaque"} LOD 200 CGPROGRAM #pragma surface surf Lambert sampler2D _MainTex; struct Input { float2 uv_MainTex; }; void surf (Input IN, inout SurfaceOutput o) { fixed3 lightDir = normalize(UnityLight.dir); float lambertTerm = dot(normalize(o.Normal), lightDir); o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb * lambertTerm; o.Alpha = tex2D (_MainTex, IN.uv_MainTex).a; } ENDCG } } ``` 这个shader使用了Lambert光照模型来计算每个像素的颜色,实现了基本的光照效果。在顶点着色器中,我们通过获取_MainTex纹理的uv坐标,并将其存储在Input结构体中。然后,在surf函数中,我们使用tex2D函数来获取_MainTex纹理的颜色值,并将其赋给SurfaceOutput结构体的Albedo成员,以实现纹理贴图。在片段着色器中,我们通过获取UnityLight的dir属性来获取光照的方向,并将其与表面法线计算出的兰伯特项相乘,得到每个像素的最终颜色。最后,我们将最终颜色赋给SurfaceOutput结构体的Albedo成员,以实现光照效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cchoop

有用的话请杯肥宅水

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值