MME编写入门(四)一个简单光照模型
小林呓
2023年03月09日 21:58
收录于文集
共8篇

纠结了半天,错误和各种不规范,最后看到一句话:计算机图形学的第一定律:如果他看起来是对的,那么他就是对的。

《3D数学基础:图形与游戏开发》的作者邓恩说的。

“他的话看起来是对的,所以是对的。”我这么理解道。

代码块
clike
自动换行
复制代码
float4x4 WorldViewMatrix		: WORLDVIEW;
float4x4 WorldViewProjMatrix	: WORLDVIEWPROJECTION;
float4x4 WorldMatrix			: WORLD;
//需要用到的矩阵
//需要用到光的位置、相机位置
float3   LightDirection    : DIRECTION < string Object = "Light"; >;
float3   CameraPosition    : POSITION  < string Object = "Camera"; >;

//需要用到物体的材质色
//漫反射、环境色、自发光色、镜面反射色
float4   MaterialDiffuse   : DIFFUSE  < string Object = "Geometry"; >;
float3   MaterialAmbient   : AMBIENT  < string Object = "Geometry"; >;
float3   MaterialEmmisive	: EMISSIVE	< string Object = "Geometry"; >;
float3   MaterialSpecular	: SPECULAR	< string Object = "Geometry"; >;
//镜面反射强度、toon色
float    SpecularPower		: SPECULARPOWER < string Object = "Geometry"; >;
float3   MaterialToon      : TOONCOLOR;
//需要的光源颜色,取三份,方便下面计算
float3   LightDiffuse      : DIFFUSE   < string Object = "Light"; >;
float3   LightAmbient      : AMBIENT   < string Object = "Light"; >;
float3   LightSpecular     : SPECULAR  < string Object = "Light"; >;
//计算漫反射颜色、环境色颜色、镜面反射颜色
static float4 DiffuseColor  = MaterialDiffuse  * float4(LightDiffuse, 1.0f);
static float3 AmbientColor  = saturate(MaterialAmbient  * LightAmbient + MaterialEmmisive);
static float3 SpecularColor = MaterialSpecular * LightSpecular;
//先定义,后面调用
bool UseTexture;
bool UseToon ;   
//材质纹理和采样器
texture ObjectTexture: MATERIALTEXTURE;
sampler ObjTexSampler = sampler_state {
    texture = <ObjectTexture>;
    MINFILTER = LINEAR;
    MAGFILTER = LINEAR;
    ADDRESSU  = WRAP;
    ADDRESSV  = WRAP;
};
//结构体,准备需要的数据
struct VS_OUTPUT
{
    float4 Pos        : POSITION;    
    float2 Tex        : TEXCOORD1;   
    float3 Normal      : TEXCOORD2;   
    float3 Eye        : TEXCOORD3;   
};
//顶点着色器部分
VS_OUTPUT Basic_VS( float4 Pos : POSITION, float3 Normal : NORMAL, float2 Tex : TEXCOORD0)
{
//这里把pos另存了一份      
	float4 Pos0 = Pos;
//位置变换
    Pos = mul( Pos, WorldViewProjMatrix );
//视线位置变换
    float3 Eye = CameraPosition - mul( Pos0, WorldMatrix );
//顶点法线
    Normal = normalize( mul( Normal, WorldMatrix ) );
//输出 
    VS_OUTPUT Out = { Pos, Tex, Normal, Eye };
    return Out;
}

//像素着色器部分
float4 Basic_PS( float2 Tex : TEXCOORD1, float3 Normal : TEXCOORD2, float3 Eye : TEXCOORD3 ) : COLOR0
{
    float4 Color = 0;
//漫反射和环境色计算
    Color.rgb = saturate( max(0,dot( Normal, -LightDirection )) * DiffuseColor.rgb + AmbientColor );
    Color.a = DiffuseColor.a;
//镜面反射计算
    float3 HalfVector = normalize( normalize(Eye) + -LightDirection );
    float3 Specular = pow( max(0,dot( normalize(Normal),HalfVector )), SpecularPower ) * SpecularColor;
 //纹理应用
    Color *= tex2D( ObjTexSampler, Tex );
 //TOON应用
    if (UseTexture = true) {
        float LightNormal = dot(Normal,-LightDirection);
        Color.rgb *= lerp(MaterialToon, float3(1,1,1), saturate(LightNormal * 16 + 0.5));
    }
//镜面反射色加入
    Color.rgb += Specular;
//最终输出
    return Color;
}

//绘制
technique BasicLight < string MMDPass = "object_ss"; > {
    pass DrawObject
    {
        VertexShader = compile vs_2_0 Basic_VS();
        PixelShader  = compile ps_2_0 Basic_PS();
    }
}

复制成功

嗯……大概是对的吧……这个光照模型还缺少Sph,不过问题倒也不大。

代码块
clike
自动换行
复制代码
float4   SphereAddValue    : ADDINGSPHERETEXTURE;
float4   SphereMulValue    : MULTIPLYINGSPHERETEXTURE;
复制成功

嗯……下次再考虑加上spa吧。看舞力介入的full.fx中是将影色单独拿出来写的……

首先是准备数据。这次需要的数据比上次多很多。

矩阵是必须的。接下来是这些。(唉我是真的麻了……我写的整整齐齐的代码扔进这个网页就不给我对齐,手动对齐好几遍还不齐)

代码块
clike
自动换行
复制代码
float3   LightDirection    : DIRECTION < string Object = "Light"; >;
float3   CameraPosition    : POSITION  < string Object = "Camera"; >;
复制成功

代码块
clike
自动换行
复制代码
float4   MaterialDiffuse    : DIFFUSE  < string Object = "Geometry"; >;
float3   MaterialAmbient    : AMBIENT  < string Object = "Geometry"; >;
float3   MaterialEmmisive   : EMISSIVE < string Object = "Geometry"; >;
float3   MaterialSpecular	 : SPECULAR < string Object = "Geometry"; >;
float    SpecularPower		: SPECULARPOWER < string Object = "Geometry"; >;
float3   MaterialToon		 : TOONCOLOR;

float3   LightDiffuse      : DIFFUSE   < string Object = "Light"; >;
float3   LightAmbient      : AMBIENT   < string Object = "Light"; >;
float3   LightSpecular     : SPECULAR  < string Object = "Light"; >;
复制成功

参考MME手册。即可明白书写的格式。把语义写对了就没太大问题。

颜色计算。static的作用应该和C语言里的一样,用来说明静态变量,声明了static后变量就是内部变量。

代码块
clike
自动换行
复制代码
static float4 DiffuseColor  = MaterialDiffuse  * float4(LightDiffuse, 1.0f);
static float3 AmbientColor  = saturate(MaterialAmbient  * LightAmbient + MaterialEmmisive);
static float3 SpecularColor	 = MaterialSpecular * LightSpecular;
复制成功

简单光照模型,假定物体为非透明物体,物体表面所呈现的颜色仅由反射光决定,不考虑透射光。反射光部分被细分为漫反射光 DiffuseColor 和 镜面反射光SpecularColor两种。

简单光照模型分为 环境光模型、漫反射光模型、镜面反射光模型,全部属于经验模型,表现为:

I表示物体表面上一点反射到视点的光强,e、d、s分别为环境光光强、漫反射光光强、镜面反射光光强。

材质部分,要考虑材质属性,材质属性是指物体表面对光的吸收、反射和透射的性能。由于是简单光照模型,所以只考虑材质的反射属性。

镜面反射光较为特殊,要考虑镜面反射率,所以引用了float SPECULARPOWER

代码块
clike
自动换行
复制代码
float    SpecularPower     : SPECULARPOWER < string Object = "Geometry"; >;
复制成功

然后是

代码块
clike
自动换行
复制代码
bool UseTexture;
bool UseToon ;  
复制成功

然后是采样器。

代码块
clike
自动换行
复制代码
texture ObjectTexture: MATERIALTEXTURE;
sampler ObjTexSampler = sampler_state {
    texture = <ObjectTexture>;
    MINFILTER = LINEAR;
    MAGFILTER = LINEAR;
	ADDRESSU  = WRAP;
    ADDRESSV  = WRAP;
};
复制成功

如果要调用纹理,那么就需要采样器。

然而纹理,是没法直接使用的。需要滤波。不理解也没关系,这东西绝大部分情况下都不用改,复制粘贴调用就行。

代码块
clike
自动换行
复制代码
       MinFilter = Linear; //缩小图形使用线性滤波
        MagFilter = Linear; //放大图形使用线性滤波
        AddressU = Wrap; //U方向上的纹理寻址模式采用Wrap方式
        AddressV = Wrap; //V方向上的纹理寻址模式采用Wrap方式
复制成功

代码块
clike
自动换行
复制代码
struct VS_OUTPUT
{
    float4 Pos        : POSITION;    
    float2 Tex        : TEXCOORD1;   
    float3 Normal     : TEXCOORD2;   
    float3 Eye        : TEXCOORD3;   
};
复制成功

结构体准备我们需要的用到的数据。然后是顶点着色,输入输出都很正常。这里我们呢需要准备 一个Eye的视点位置的数据。这个视点位置这样计算。

代码块
clike
自动换行
复制代码
		float3 Eye = CameraPosition - mul( Pos0, WorldMatrix );
		Normal = normalize( mul( Normal, WorldMatrix ) );
复制成功

Normal 顶点法线的转换,然后单位化。

这里用到了新的函数normalize(x),意思是返回单位化向量,定义为 x / length(x)。

然后是像素着色器的部分。

首先得定义个Color,后面才能用。

Color.rgb,意思是取得Color的前三维,我在上一篇有说但是没举例子。

举个例子,如果float4 Color = float4(1,0.2,0.3,1),那么Color.rgb 的意思就是调用1,0.2,0.3,这三维。Color.r,同理,调用第一维。

同理,xyzw,也可以。Color.xyzw 和 Color.rgba等效,但是不能混着用。

代码块
clike
自动换行
复制代码
    float4 Color = 0;
    Color.rgb = saturate( max(0,dot( Normal, -LightDirection )) * DiffuseColor.rgb + AmbientColor );
    Color.a = DiffuseColor.a;
复制成功

这里用到了新的函数saturate(x),意思是把x截取到[0, 1]之间。

函数max(x, y),意思是取x,y两者中较大的。

这里是计算物体的rgb值。就是物体的法向*漫反射颜色+环境色。

接下来计算反射光颜色。这里用到了一个半程向量,通过对观察向量和光向量取平均然后归一化得到一个新的向量,新向量称为半程向量(bisector),使用半程向量与法线点乘来计算高光。

代码块
clike
自动换行
复制代码
 float3 HalfVector = normalize( normalize(Eye) + -LightDirection );
 float3 Specular = pow( max(0,dot( normalize(Normal),HalfVector )), SpecularPower ) *SpecularColor;
    
复制成功

这里有个新函数pow,pow(x, y),意思是x^y。

使用半程向量,是为了简化计算。

图片源于《三维计算机图形学》第251页,作者:孔令德 康凤娥

Phong提出的一个计算镜面反射光的经验公式,成为Phong模型。模型上一点P的镜面反射光强I_%7Bs%7D%20为:I_%7Bs%7D%3Dk_%7Bs%7D%20I_%7Bp%7D%20%5Ccos%5En%20%20%5Calpha%20

I_%7Bp%7D%20为入射光强,k_%7Bs%7D%20为镜面反射率

对于单位向量R、V,有%5Ccos%20%20%5Calpha%20%3DR%5Ccdot%20V,考虑有大于90的情况,R·V的结果为负,应该取0,所以改写为

I_%7Bs%7D%20%3Dk_%7Bs%7D%20I_%7Bp%7Dmax(R%5Ccdot%20V%2C0%20)%20%5En

不难看出镜面反射的光强不仅取决于物体表面的法线方向,而且依赖于光远与视线的相对位置。只有当视点位于比较合适的位置时,才可以观察到物体表面某些区域呈现的高光。所以在简单光照模型中,“视点固定,旋转物体”和“视点旋转,物体固定”所看到的物体是有差别的。

V的计算很简单,R的计算稍微复杂。

总结下来,Phong模型的计算非常复杂,再加上环境光和漫射光,会让式子变得更长。

Blinn指出中分向量H的方向是最大反射光强方向,对模型进行改进,改进后的模型成为Blinn-Phong模型。Blinn用N·H代替R·V,其中H为中分向量,是单位光向量L和单位观察向量V的平分向量。

改进后的公式表述为I_%7Bs%7D%20%3Dk_%7Bs%7D%20I_%7Bp%7Dmax(N%5Ccdot%20H%2C0%20)%20%5En

代码块
clike
自动换行
复制代码
    Color *= tex2D( ObjTexSampler, Tex );
   
    if (UseTexture = true) {
        float LightNormal = dot(Normal,-LightDirection);
        Color.rgb *= lerp(MaterialToon, float3(1,1,1), saturate(LightNormal * 16 + 0.5));
    }

    Color.rgb += Specular;
	
复制成功

这里用到了新的函数tex2D(s,t),意思是返回纹理s在t位置的颜色。

接下来做个判断,woc我这里写错了,前边的插入的代码块不知道怎么变更了……麻烦把这个UseTexture改成UseToon,这里是在判断是否使用toon,做了一个插值计算。

代码块
clike
自动换行
复制代码
    if (UseToon = true) 
 
复制成功

虽然可能对结果影响不大,因为现在绝大部分模型都有toon。

这里用了一个新函数lerp,lerp(x, y, s),对x、y进行插值计算。Returns x + s(y - x)。

最后是个pass

代码块
clike
自动换行
复制代码
technique BasicLight < string MMDPass = "object_ss"; > {
    pass DrawObject
    {
        VertexShader = compile vs_2_0 Basic_VS();
        PixelShader  = compile ps_2_0 Basic_PS();
    }
}
复制成功

注意