1、Unity光源模型

1.1 两种渲染方式

前向渲染:每个物体都有可能被重复渲染,即基于物体的光源渲染;超出灯光设置数量被当作顶点光(6*6)

延迟渲染:基于光源的物体渲染;以灯光为单位,先计算灯光所需东西,如RT0为漫反射,RT1为金属度,RT2为法线图;对于多光源友好(6+6);

区别:延迟渲染需要MRT技术(multi render target)和空间容量大

1.2 前向渲染

Unity两种前向渲染管线

bulid in:跟上文提到的概念一样;  个pass;base仅一个方向光。

urp:一个pass,最多八盏灯;

1.3 Phong光源模型 

高光反射:pow(RdotV,smoothness)×lightcolor;

漫反射:NdotL×lightcolor

若附加纹理,相乘即可。

1.4 法线贴图

unity中的切线a分量是为了兼容各平台差异

法线贴图需要解码,解码后调整隆起大小再转至TBN空间

反射为什么灯光要负方向?

因为unity内置的计算灯光方向是从模型向光源走

forwardbase的实现:

Shader "Unlit/phong2"
{Properties{_MainTex ("Texture", 2D) = "white" {}_smoothness("Smoothness",Range(0.01,100))=1.0_specuIntensity("SpecuIntensity",Range(0.00,100))=1.0_NormalTex ("NormalTex", 2D) = "white" {}_normalIntensity("NormalIntensity",Range(0.00,5))=1.0}SubShader{LOD 100Pass{Tags { "LightMode"="ForwardBase" }CGPROGRAM#pragma vertex vert#pragma fragment frag#pragma multi_compile_fwdbase#include "AutoLight.cginc"#include "UnityCG.cginc"struct appdata{float4 vertex : POSITION;float2 uv : TEXCOORD0;float3 nor:NORMAL;float4 tangent:TANGENT;};struct v2f{float2 uv : TEXCOORD0;float4 vertex : SV_POSITION;float3 posW:TEXCOORD1;float3 norW:TEXCOORD2;float3 tangentW:TEXCOORD3;float3 biNormal:TEXCOORD4;};sampler2D _MainTex;float4 _MainTex_ST;float4 _LightColor0;float _smoothness;float _specuIntensity;float _normalIntensity;sampler2D _NormalTex;float4 _NormalTex_ST;v2f vert (appdata v){v2f o;o.vertex = UnityObjectToClipPos(v.vertex);o.posW=mul(unity_ObjectToWorld,v.vertex);o.norW=normalize(mul(float4(v.nor,0.0),unity_ObjectToWorld).xyz);o.uv = TRANSFORM_TEX(v.uv, _MainTex);o.tangentW=normalize(mul(unity_ObjectToWorld,float4(v.tangent.xyz,0.0)).xyz)*v.tangent.w;o.biNormal=normalize(cross(o.tangentW,o.norW));return o;}half4 frag (v2f i) : SV_Target{half3 final;//normal mapfloat4 normalmMap=tex2D(_NormalTex,i.uv);float3 normal=UnpackNormal(normalmMap).xyz;normal.xy=normal.xy*_normalIntensity;float3 tangentW=normalize(i.tangentW);float3 biNormal=normalize(i.biNormal);float3 norW=normalize(i.norW);float3x3 TBN=float3x3(tangentW,biNormal,norW);normal=normalize(mul(normal,TBN)) ;// ambientfixed4 baseColor = tex2D(_MainTex, i.uv);half3 ambient=baseColor.rgb*UNITY_LIGHTMODEL_AMBIENT.rgb;//DIFFUSE//float3 normalW=normalize(i.norW);float3 L=normalize(UnityWorldSpaceLightDir(i.posW));float NdotL=saturate(dot(L,normal));//half3 diffuse=NdotL*baseColor.rgb*_LightColor0.xyz;//SPECULARfloat3 R=reflect(-L,normal);//float3 viewDirW=normalize(UnityWorldSpaceViewDir(i.posW));float3 specu=pow(saturate(dot(R,viewDirW)),_smoothness)*_LightColor0.xyz*_specuIntensity;final=diffuse.rgb+ambient.rgb+specu.rgb;return half4(final,1.0);}ENDCG}}
}

forwardadd的实现:

Tags { "LightMode"="ForwardAdd" }Blend One OneCGPROGRAM#pragma vertex vert#pragma fragment frag#pragma multi_compile_fwdadd#include "AutoLight.cginc"#include "UnityCG.cginc"struct appdata{float4 vertex : POSITION;float2 uv : TEXCOORD0;float3 nor:NORMAL;float4 tangent:TANGENT;};struct v2f{float2 uv : TEXCOORD0;float4 vertex : SV_POSITION;float3 posW:TEXCOORD1;float3 norW:TEXCOORD2;float3 tangentW:TEXCOORD3;float3 biNormal:TEXCOORD4;};sampler2D _MainTex;float4 _MainTex_ST;float4 _LightColor0;float _smoothness;float _specuIntensity;float _normalIntensity;sampler2D _NormalTex;float4 _NormalTex_ST;v2f vert (appdata v){v2f o;o.vertex = UnityObjectToClipPos(v.vertex);o.posW=mul(unity_ObjectToWorld,v.vertex);o.norW=normalize(mul(float4(v.nor,0.0),unity_ObjectToWorld).xyz);o.uv = TRANSFORM_TEX(v.uv, _MainTex);o.tangentW=normalize(mul(unity_ObjectToWorld,float4(v.tangent.xyz,0.0)).xyz)*v.tangent.w;o.biNormal=normalize(cross(o.tangentW,o.norW));return o;}half4 frag (v2f i) : SV_Target{half3 final;//光源衰减#if defined(DIRECTIONAL)float3 L=normalize(UnityWorldSpaceLightDir(i.posW));float  attenuation=1.0;#elif defined(POINT)float3 L=normalize(_WorldSpaceLightPos0.xyz-i.posW);float distance=length(_WorldSpaceLightPos0.xyz-i.posW);float Range=1.0/unity_WorldToLight[0][0];float  attenuation=saturate(Range-distance/Range);#endif//光源衰减//normal mapfloat4 normalmMap=tex2D(_NormalTex,i.uv);float3 normal=UnpackNormal(normalmMap).xyz;normal.xy=normal.xy*_normalIntensity;float3 tangentW=normalize(i.tangentW);float3 biNormal=normalize(i.biNormal);float3 norW=normalize(i.norW);float3x3 TBN=float3x3(tangentW,biNormal,norW);normal=normalize(mul(normal,TBN)) ;// ambientfixed4 baseColor = tex2D(_MainTex, i.uv);half3 ambient=baseColor.rgb*UNITY_LIGHTMODEL_AMBIENT.rgb;//DIFFUSE//float3 normalW=normalize(i.norW);float NdotL=saturate(dot(L,normal));//half3 diffuse=NdotL*baseColor.rgb*_LightColor0.xyz;//SPECULARfloat3 R=reflect(-L,normal);//float3 viewDirW=normalize(UnityWorldSpaceViewDir(i.posW));float3 specu=pow(saturate(dot(R,viewDirW)),_smoothness)*_LightColor0.xyz*_specuIntensity;final=(diffuse.rgb+specu.rgb)*attenuation;return half4(final,1.0);}

点光源衰减范围:r-d/r

bliphong,在phong模型基础上优化高光反射,减少消耗,(半角向量)h=l+v,NdotH

// phong// float3 R=reflect(-L,normal);// float RdotN=saturate(dot(R,norW));// float3 specu=pow(RdotN,_smoothness)*_LightColor0.xyz*_specuIntensity;//bliphongfloat3 viewDirW=normalize(UnityWorldSpaceViewDir(i.posW));float3 h=max(0.0,L+viewDirW);float HdotN=saturate(dot(h,norW));float3 specu=pow(HdotN,_smoothness)*_LightColor0.xyz*_specuIntensity;

BliPhong                                                        Phong

2、ACESFilm tone-mapping:

显示器显示范围为0-1,为LDR,而计算机渲染可以是任意数值。HDR→LDR过程造成失真,丢失许多细节。(HDR:高动态范围)

解决这个问题:HDR→LDR(tone-mapping)

tone-mapping(个人经验),pow(basecolor(diffuse2d),2.2)转换至线性空间,pow(tonecolor,1.0/2.2)转换至伽马空间。

ACESFilm                                                                  not

全局环境光开启泛光?解决:globalAmbient×baseColor;

3、视差贴图(原理还是不熟悉):

置换贴图,模型表面曲面细分,高密度顶点,读取高度图,顶点偏移。深度图,其实是高度图的反相,白色代表凹陷

for (int i = 0; i < 10; i++)
{half height = tex2D(_ParallaxMap, uv_parallax);uv_parallax = uv_parallax+ (1.0 - height) * view_tangentspace.xy * _Parallax * 0.01f;
}

开启                                                                        未开启

4、shadowmap

  • shadowmap
  • screen space shadowmap
  • 联级阴影(csm) cascaded shadowmapping:shadowmap基础上,视锥体分级

shadowmap就是一张深度图,

shadowmap实现:光源相机渲染深度信息,物体转换至光源相机空间,跟shadowmap里的深度值作比较,若片元深度值>深度值(shadowmap),光面。反之阴影面。

联级阴影以及屏幕空间下的阴影还需深入学习!

注意:光位置的w分量可以判断是否为平行光,0为平行光。1为点光。

5、间接光照

5.1基本概念

环境光≈间接光

三种技术实现:

  • LightMap(预先烘焙好)
  • 反射探针:捕抓环境信息→环境贴图,模拟漫反射
  • 光照探针:预先收集环境和光源的漫反射信息,模拟漫反射;

实现原理:

  • 环境贴图
  • IBL基于图像的照明
  • SH球谐光照

CubeMap采样全景图实现:

反射探针和CubeMap实现:

fixed4 frag (v2f i) : SV_Target
{// sample the texturefloat3 viewDir=UnityWorldSpaceViewDir(i.posW);float3 normalW=normalize(i.normalW);float3 ref=reflect(-viewDir,normalW);//反射探针float4 environColor=UNITY_SAMPLE_TEXCUBE(unity_SpecCube0,ref);//为了移动端也可以获取到HDR的信息float3 hdrColor=DecodeHDR(environColor,unity_SpecCube0_HDR);//fixed4 col = texCUBE(_CubeMap, ref);//CubeMapreturn float4(hdrColor,1.0);
}

6、IBL 基于图像的照明

ibl ,一个设置一行代码。

为什么是mipmap?

pow对比度,直接相乘亮度,lerp整体粗糙度,线性粗糙度

ibl镜面反射和漫反射

使用IBL要三线性过滤

IBL实现一般不需要大尺寸(压缩压缩再压缩,,,,)

IBL间接镜面反射光:

fixed4 frag (v2f i) : SV_Target
{// 取rougness,算出mipmap的levelfloat roughness=tex2D(_RoughtMap,i.uv).r;roughness=pow(roughness,_Contrastion)*_Brighteness;roughness=lerp(_RoughnessMin,_RoughnessMax,roughness);roughness = roughness * (1.7 - 0.7 * roughness);float level=roughness*6.0;//取法线,算反射float3 normaldata= UnpackNormal(tex2D(_NormalMap,i.uv));float3 normalW=normalize(i.normalW);float3 tangent=normalize(i.tangent);float3 binormal=normalize(i.binormal); float3x3 TBN=float3x3(tangent,binormal,normalW);float3 normal=normalize(mul(normaldata,TBN));float3 viewDir=UnityWorldSpaceViewDir(i.posW);float3 reflectDir=reflect(-viewDir,normal);//aofloat aoMap=tex2D(_AOMap,i.uv).r;float ao= lerp(1.0,aoMap,_AOAdjust);//cubemap的ibl实现float4  cubeMap=texCUBElod(_CubeMap,float4(reflectDir,level));float3  envir=DecodeHDR(cubeMap,_CubeMap_HDR).rgb;//cubemap的probe实现//half4 color_cubemap = UNITY_SAMPLE_TEXCUBE_LOD(unity_SpecCube0, reflectDir, level);//half3 envir = DecodeHDR(color_cubemap, unity_SpecCube0_HDR);//确保在移动端能拿到HDR信息//最终颜色float3  col=envir*_Tint.rgb*_Tint.rgb*ao* _Expose;//tonemappingcol=pow(col,2.2);col=ACES_Tonemapping(col);col=pow(col,1/2.2);return float4(col,1.0);
}

IBL间接漫反射光: 

float4 uv_ibl = float4(normal_dir, mip_level);half4 color_cubemap = texCUBElod(_CubeMap, uv_ibl);
half3 env_color = DecodeHDR(color_cubemap, _CubeMap_HDR);//确保在移动端能拿到HDR信息

7、球谐光照(SH)

sh球谐光照内置实现(light probe的probe)

在forwardbase实现;

造轮子实现:

half4 frag (v2f i) : SV_Target
{half3 normal_dir = normalize(i.normal_world);half3 normaldata = UnpackNormal(tex2D(_NormalMap,i.uv));normaldata.xy = normaldata.xy* _NormalIntensity;half3 tangent_dir = normalize(i.tangent_world);half3 binormal_dir = normalize(i.binormal_world);normal_dir = normalize(tangent_dir * normaldata.x+ binormal_dir * normaldata.y + normal_dir * normaldata.z);half ao = tex2D(_AOMap, i.uv).r;ao = lerp(1.0,ao, _AOAdjust);float4 normalForSH = float4(normal_dir, 1.0);//SHEvalLinearL0L1half3 x;x.r = dot(custom_SHAr, normalForSH);x.g = dot(custom_SHAg, normalForSH);x.b = dot(custom_SHAb, normalForSH);//SHEvalLinearL2half3 x1, x2;// 4 of the quadratic (L2) polynomialshalf4 vB = normalForSH.xyzz * normalForSH.yzzx;x1.r = dot(custom_SHBr, vB);x1.g = dot(custom_SHBg, vB);x1.b = dot(custom_SHBb, vB);// Final (5th) quadratic (L2) polynomialhalf vC = normalForSH.x*normalForSH.x - normalForSH.y*normalForSH.y;x2 = custom_SHC.rgb * vC;float3 sh = max(float3(0.0, 0.0, 0.0), (x + x1 + x2));sh = pow(sh, 1.0 / 2.2);half3 env_color = sh;half3 final_color = env_color * ao * _Tint.rgb * _Expose;return float4(final_color,1.0);
}

unity内置实现:

half4 frag (v2f i) : SV_Target
{half3 normal_dir = normalize(i.normal_world);half3 normaldata = UnpackNormal(tex2D(_NormalMap,i.uv));normaldata.xy = normaldata.xy* _NormalIntensity;half3 tangent_dir = normalize(i.tangent_world);half3 binormal_dir = normalize(i.binormal_world);normal_dir = normalize(tangent_dir * normaldata.x+ binormal_dir * normaldata.y + normal_dir * normaldata.z);half ao = tex2D(_AOMap, i.uv).r;ao = lerp(1.0,ao, _AOAdjust);half3 env_color = ShadeSH9(float4(normal_dir,1.0));half3 final_color = env_color * ao * _Tint.rgb * _Expose;return float4(final_color,1.0);
}

unity全局间接光:

  • lighting-environment lighting        漫反射
  • lighting-environment refection      镜面反射

lighting-mixed lighting-baked global illumination        烘焙多光源

8、玉石模拟

玉石光线(穿透感)+光滑质感(环境反射)

组成:漫反射+透射+环境光;

漫反射=phong漫反射+addcolor+竖直方向AO;

透射计算:VdotB(B为L扭曲而成),结合对比度、亮度,乘以模型厚度、basecolor(光源颜色影响世界,所以独立一个光源颜色)和平行光颜色;

环境光:cubemap结合菲涅尔;

forbase:

float4 frag(v2f i) : COLOR
{//infofloat3 diffuse_color = _DiffuseColor;float3 normalDir = normalize(i.normalDir);float3 viewDir = normalize(_WorldSpaceCameraPos - i.posWorld.xyz);float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);//diffusefloat diff_term = max(0.0, dot(normalDir, lightDir));float3 diffuselight_color = diff_term * diffuse_color * _LightColor0.rgb;//竖直方向的AO,上亮下暗float sky_sphere = (dot(normalDir,float3(0,1,0)) + 1.0) * 0.5;float3 sky_light = sky_sphere * diffuse_color;float3 final_diffuse = diffuselight_color + sky_light * _Opacity + _AddColor.xyz;//trans lightfloat3 back_dir = -normalize(lightDir + normalDir * _BasePassDistortion);float VdotB = max(0.0, dot(viewDir, back_dir));float backlight_term = max(0.0,pow(VdotB, _BasePassPower)) * _BasePassScale;float thickness = 1.0 - tex2D(_ThicknessMap, i.uv).r;float3 backlight = backlight_term * thickness *_LightColor0.xyz * _BasePassColor.xyz;//ENVfloat3 reflectDir = reflect(-viewDir,normalDir);half theta = _EnvRotate * UNITY_PI / 180.0f;float2x2 m_rot = float2x2(cos(theta), -sin(theta), sin(theta),cos(theta));float2 v_rot = mul(m_rot, reflectDir.xz);reflectDir = half3(v_rot.x, reflectDir.y, v_rot.y);float4 cubemap_color = texCUBE(_EnvMap,reflectDir);half3 env_color = DecodeHDR(cubemap_color, _EnvMap_HDR);float fresnel = 1.0 - saturate(dot(normalDir, viewDir));fresnel = smoothstep(_FresnelMin, _FresnelMax, fresnel);float3 final_env = env_color * _EnvIntensity * fresnel;//combinefloat3 combined_color = final_diffuse + final_env + backlight;float3 final_color = combined_color;return float4(final_color,1.0);
}

forwardadd:

float4 frag(v2f i) : COLOR
{float3 diffuse_color = _DiffuseColor * _DiffuseColor;float3 normalDir = normalize(i.normalDir); float3 viewDir = normalize(_WorldSpaceCameraPos - i.posWorld.xyz);float NdotV = saturate(dot(normalDir,viewDir));//light infofloat3 lightDir = normalize(lerp(_WorldSpaceLightPos0.xyz, _WorldSpaceLightPos0.xyz - i.posWorld.xyz,_WorldSpaceLightPos0.w));float attenuation = LIGHT_ATTENUATION(i);//trans lightfloat3 back_dir = -normalize(lightDir + normalDir * _AddPassDistortion);float VdotB = max(0.0,dot(viewDir, back_dir));float backlight_term = max(0.0, pow(VdotB, _AddPassPower)) * _AddPassScale;float thickness = 1.0 - tex2D(_ThicknessMap, i.uv).r;float3 backlight = backlight_term * thickness *_LightColor0.xyz * _AddPassColor.xyz;//combinefloat3 final_color = backlight;final_color = sqrt(final_color);return float4(final_color,1.0);
}

9、移动端角色渲染

分析与问题:

  • ue和unity的单位区别(米、厘米);
  • x轴向的区别;
  • 模型和骨骼动画的大量到处;
  • Mc纹理,rg通道粗糙度、金属度;
  • 角色脚部抖动,曲线优化和压缩问题;

细节:

  • 皮肤区域高光弱,金属区域高光强,albedo贴图的使用原理其一
  • 皮肤区域漫反射强,金属区域漫反射弱,albedo贴图的使用原理其二
  • 粗糙度做文章,越粗糙,光滑度越低
  • 阴影不在间接光中计算,仅在直接光中,因为间接光的作用在于提亮暗部
  • 皮肤正常sh,其他乘以0.5sh

疑问:

  1. 间接光的漫反射为什么要限定为0.5到1的范围内?

解答:使用了半兰伯特模型,提亮暗部

2. 153/154的操作不是很能理解,间接光都要乘以这个?

解答:shininess x smoothness ≈ a(1-a)x+a²,a为smoothness,但是还是线性关系

3.  皮肤柔光的插值操作

公式总结:

  • 直接光漫反射:diffterm x basecolor x atten x lightcolor
  • 直接光镜面反射:specterm x speccolor x atten x lightcolor
  • 间接光漫反射:sh x basecolor x halflambert
  • 间接光镜面反射:ibl x expose x speccolor x halflambert

头发渲染:kk的各向异性高光渲染

技术原理:normal实现的高光仅仅是一点,我们需要在头发的横截面都有高光

公式:T、L、V三个成关系,LV成半角关系H,求sin(T,H);

细节:

  • T根据normal进行偏移,高光即可实现偏移;
  • 噪声图扰动各向异性方向,范围由0到1缩放到-1到1;
  • N x noise
  • half_lambert/NdotL ,防止背光区域也有高光

疑问:

1. 为什么各向异性高光只亮一处?

因为sin(T,H)的夹角为90°时才为1

2. 为什么成光盘一样的扇形?

因为顺着边缘变化,中间的偏移更大,两边渐变,越远渐变范围越大;而中心范围小是因为没有偏移

细节语法:

1. 宏定义:检查器隐藏

2. 宏开关:多重编译和shader特性,增加一个,代码量两倍

unity Build-in 光源和光照模型相关推荐

  1. 如何在Unity中自定义光源,包含URP管线和Build in 管线(一)

    如何在Unity中自定义光源,包含URP管线和Build in 管线(一) 众所周知,光照在游戏画面效果上占了很大比例,一个游戏画面好不好,用最简单的理解来说,就是看游戏画面亮不亮,当然这个亮不是不是 ...

  2. [unity] build项目报错:Currently selected scripting backend (.NET)is not installed

    [Unity] Currently selected scripting backend .NET is notinstalled Unity build项目报错,没有安装 .NET 问题概况 Uni ...

  3. unity build设置_Microsoft Build上的Unity:展会上的7个重点

    unity build设置 The Microsoft Build 2018 developer conference just wrapped up in Seattle and Unity was ...

  4. unity球体添加光源_Unity渲染路径——光源种类

    在之前的实践场景中,一般只有一个光源且光源类型是平行光.但实际光源类型不止一种,且需要被区别对待. 光源种类 在之前的文章中多多少少地提到了Unity中支持几种不同的光源,其中包括点光源(point ...

  5. unity build报错Type has an extra field of type in the and thus can‘t be serialized error

    一般发生于unity的android打包中,删除Library文件夹下的Bee文件夹和BuildPlayerData文件夹重新build即可,unity版本2021.3.13f1c1

  6. unity球体添加光源_关于Unity中的光源

    一.光源定义 光源,是一个普通节点加一个Light组件,创建的时候可以直接创建光源节点,也可以先创建一个空节点,再添加Light组件实例. 二.颜色形成 看到的物体颜色受两个很重要的因素的影响,一个是 ...

  7. Unity build setting小技巧

    1.如何隐藏分辨率选择窗口(如果需要的话运行程序时按住shift按键可呼出) 2.如果某些特殊设备分辨率怎么都调不对,不妨看看这里勾选了没有 3.在这里可以更改程序运行时的'made with uni ...

  8. Unity Build IOS包

    今天接触了Unity和IOS的交互,最主要是学习了UnitybuildIOS包,实际情况呢可以参考别人的博客 https://blog.csdn.net/jia18337935154/article/ ...

  9. Unity中的多光源

    在Unity中,如果想要使用多光源,比如2个平行光,或者1个平行光+1个点光源,需要在额外的shader pass中进行处理: Pass {Tags {"LightMode" = ...

最新文章

  1. runaway深度递归函数测试及相关汇编指令
  2. svg text换行_5分钟看懂SVG反爬虫原理与绕过实战 | 知了干货分享
  3. 吊打一切现有开源OCR项目:效果再升7%,速度提升220%
  4. 最快最新最详细的IT电子书
  5. with 关键字实现递归查询
  6. Wpf 调用线程无法访问此对象,因为另一个线程拥有该对象,解决方案
  7. poj 1083 Moving Tables
  8. python安装mysqldb模块_Python的MySQLdb模块安装
  9. redis 介绍与安装
  10. 内联函数与宏定义的区别
  11. 任正非——《一江春水向东流》
  12. 幽默 滑稽 及 其他
  13. Java模拟消息队列
  14. 大众点评评论抓取-CSS加密破解
  15. EVE-ng模拟器安装教程和使用教程
  16. Windows MFC 工程应用开发与框架原理完全剖析教程(下)
  17. 单片机/树莓派扩展双串口(TTL和RS485)
  18. OSI七层模型和TCPIP五层模型
  19. 外部PLC触发VisionMaster多流程运行
  20. 发现一款好用到爆的数据库工具,被惊艳到了!

热门文章

  1. 如何做一个项目.ppt
  2. 0422“数学口袋精灵”BUG发现
  3. 9018单管调频话筒调试攻略
  4. 图像处理仿真数据集汇总
  5. RGH reset glitch hack
  6. Single-CenterLoss for Face Forgery Detection论文阅读
  7. 【宋飞】 java 3D学习笔记
  8. 生产前端控制台报504的解决思路
  9. 微信“跳一跳”高分攻略
  10. 2022年华为杯建模答案参考