紧接上一篇:https://blog.csdn.net/yinhun2012/article/details/80912620
之前两篇博客,我们学习了一般光照模型对物体颜色的作用公式,如下:
surfaceColor = emissive + ambient + diffuse + specular;
这次我们就来实际在图形引擎中用shader cg实现一下,看下具体效果,效果图如下:
这里我做了一个放射光为鲜红色的高亮材质球,其中fragment函数是自己实现的通用光照模型计算,cg代码如下:
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unlit/FragLightUnlitShader"
{
Properties
{
_EmissiveColor("emissive",Color) = (1,1,1,1)
_AmbientColor("embient",Color) = (1,1,1,1)
_AmbientFactor("e_factor",Color) = (1,1,1,1)
_LightColor("light",Color) = (1,1,1,1)
_DiffuseFactor("d_factor",Color) = (1,1,1,1)
_SpecularFactor("s_factor",Color) = (1,1,1,1)
_SpecShininess("s_shininess",Range(10,100)) = 10
}
SubShader
{
//LightMode标注问ForwardBase是为了告诉unity引擎我们开始使用光照计算了
//需要为我们开发准备好光照计算所有数据
Tags{ "RenderType" = "Opaque" "LightMode" = "ForwardBase" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct app2vert
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct vert2frag
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
float3 _EmissiveColor; /*自己定义的放射颜色*/
float3 _AmbientColor; /*自己定义的环境光颜色,一般我们都是用unityLighting中的自带字段*/
float4 _AmbientFactor; /*自己定义的环境反射系数,为什么用一个color字段储存呢?博客中我会谈到*/
float3 _LightColor; /*自己定义的太阳光颜色,当然这个我们也是一般使用unityLighting.cginc中自带的场景中directionlight的颜色了*/
float4 _DiffuseFactor; /*自己定义的Diffuse漫反射系数,float4值*/
float4 _SpecularFactor; /*自己定义的Sepcular镜面反射系数,float4值*/
float _SpecShininess; /*自己定义的specular的强度指数*/
vert2frag vert(app2vert av)
{
vert2frag vf;
//使用mvp矩阵将顶点位置变换到投影空间位置
vf.pos = UnityObjectToClipPos(av.vertex);
//使用M矩阵将法线位置变换到模型空间法线位置的单位向量
vf.worldNormal = normalize(mul(UNITY_MATRIX_M,av.normal));
//使用M矩阵将顶点位置变换到模型空间位置的单位向量
vf.worldPos = normalize(mul(UNITY_MATRIX_M,av.vertex).xyz);
return vf;
}
fixed4 frag(vert2frag vf) : SV_Target
{
//根据lighting.cginc中的字段准备一些参数
float3 unity_buildin_ambient_light_color = UNITY_LIGHTMODEL_AMBIENT.xyz; /*unity自带的环境光颜色*/
float3 unity_buildin_world_p2sun_dir = normalize(WorldSpaceLightDir(float4(vf.worldPos,1))); /*unity自己的光线入射方向计算相反的的p点到太阳的朝向单位向量*/
float3 unity_buildin_light_color = _LightColor0.rgb; /*unity自带的入射光颜色*/
//float3 unity_buildin_light_color = _LightColor;/*或者自己指定lightColor*/
float3 unity_buildin_p2eye_dir = normalize(_WorldSpaceCameraPos.xyz - vf.worldPos.xyz); /*unity自带的eye眼睛到p点朝向单位向量*/
float3 h_dir_or_call_VplusL_dir = normalize(unity_buildin_p2eye_dir + unity_buildin_world_p2sun_dir); /*计算出的H向量的单位向量*/
/*surfaceColor = emissive + ambient + diffuse + specular根据这个公式一步一步来计算*/
//先计算emissive
float3 emissive = _EmissiveColor;
//再计算ambient
float3 ambient = _AmbientFactor.rgb * unity_buildin_ambient_light_color;
/* 或者float3 ambient = _AmbientFactor.rgb * _AmbientColor; */
//再计算diffuse
float3 diffuse = _DiffuseFactor.rgb * unity_buildin_light_color * max(dot(vf.worldNormal,unity_buildin_world_p2sun_dir),0);
//再计算specular
float3 specular = _SpecularFactor.rgb * unity_buildin_light_color * pow(max(dot(vf.worldNormal,h_dir_or_call_VplusL_dir),0),_SpecShininess);
//最终计算surfaceColor
fixed4 sCol = fixed4(emissive + ambient + diffuse + specular,1.0);
return sCol;
}
ENDCG
}
}
}
这里我来着重解释一下cg代码中可能导致小伙伴们看不懂的地方。
一.properties定义字段讲解。
我依次将放射颜色(_EmissiveColor),环境光颜色(_AmbientColor),材质环境反射系数(_AmbientFactor),场景光源颜色(_LightColor),漫反射系数(_DiffuseFactor),镜面反射系数(_SpecularFactor),镜面反射强度指数(_SpecShininess)人为定义完毕。
这里需要特别注意的是,我将系数都定义为了Color的float4类型,这是为什么呢?这些系数难道不应该是一个float类型么?
其实是这样考虑的,我们获取颜色rgb(float3)*系数(float4或者float)后分两种情况:
①.rgb(float3) * 系数(float4).xyz这种乘法得到的结果是float3(rgb.x*系数.x,rgb.x*系数.y,rgb.x*系数.z)
②.rgb(float3) * 系数(float) = float3(rgb.x*系数,rgb.x*系数,rgb.x*系数)
第一种细节更加丰富,因为rgb颜色的每个分量分别乘上了不同的系数分量值,并不像第二种一样分量的系数不变,第二种就类似于一个等比例系数,太固定死板了。
二.vert顶点函数讲解
vert顶点函数中的输入参数app2vert结构体,这个就是UnityCG.cginc文件中提供的unitybuildin字段,我们可以直接定义成输入参数结构体拿来用,这些都是unity帮我计算储存好的,此时我就使用了vertex顶点坐标和normal顶点法向量(当然注意,这是最原始的数据)
随后,在顶点函数中,我依次用矩阵处理了
①.UNITY_MATRIX_MVP去处理顶点变换到裁剪空间
②.UNITY_MATRIX_M去处理法向量到3D模型空间(这个法向量无所谓平移或者不平移,你可以用matrix3x3或者4x4的M矩阵去处理都行)并且单位化它,以便后续使用
③.UNITY_MATRIX_M去处理顶点坐标到3D模型空间坐标,后面也需要用
ps:如果现在还是不懂这些矩阵运算意义的同学,建议从我博客第一篇看起。
三.frag片段函数讲解
frag片段函数就开始我们最关键的光照模型数学计算讲解了。
首先我们准备一些参数,如下:
①.float3 unity_buildin_ambient_light_color = UNITY_LIGHTMODEL_AMBIENT.xyz;
这个字段是unity buildin定义字段,记录的是当前环境光颜色,可以通过editor - window-lighting-settings中的Environment Lighting手动设置。
②.float3 unity_buildin_world_p2sun_dir = normalize(WorldSpaceLightDir(float4(vf.worldPos,1)));
根据unity buildin的获取光线射线朝向向量的函数WorldSpaceLightDir函数获取顶点到光源朝向向量L,同时单位化。
③.float3 unity_buildin_light_color = _LightColor0.rgb;
一目了然,这就是unity buildin的入射光线颜色值
④.float3 unity_buildin_eye2p_dir = normalize(_WorldSpaceCameraPos.xyz - vf.worldPos.xyz);
这个就是计算的观察者眼睛到照射点P的朝向向量,根据场景中camera在3D建模空间xyz坐标-顶点在3D建模空间中xyz坐标。
⑤.float3 h_dir_or_call_VplusL_dir = normalize(unity_buildin_eye2p_dir + unity_buildin_world_p2sun_dir);
计算出H单位向量的值,就是V+L。这里不懂原因的同学可以返回上一篇看我写的镜面反射数学计算。
接下来就简单了,依次计算出:emissive、ambient、diffuse、specular,然后计算出surfaceColor。
这些基本是相当详细的讲解了cg代码的每一个步骤的作用,我相信小伙伴能看懂,demo我就不放了csdn要分,这个cg代码就是最核心的东西,有兴趣自己动手实现看看。
下一篇我们学习一种自定义通用光照模型的cg写法。