文章目录
- Phong
- dot - 点积的作用
- 图形了解顶点点积的作用
- 漫反射
- 高光
- reflect - 反射高光方向
- view - 观察方向
- 实践
- Shader - 着色器
- 网格模型
- References
LearnGL - 学习笔记目录
前一篇:LearnGL - 11 - 光与颜色前置篇
这篇:我们将对 Phong 光照模型实现一个简单的实现
本人才疏学浅,如有什么错误,望不吝指出。
Phong
在百度百科中也有简介:Phong光照模型
Phong 着色器模型,中文也叫:冯氏着色法,该光照模型是很简单的光照模型,只有:环境光、漫反射、高光。
- 环境光 用于模拟物体反射周围的物体颜色,在 phong 中,只是纯颜色值作为环境光直接叠加。
- 漫反射 用于模拟光入射到物体后的各种散射后,最后又从物体射出的光,在 phong 中,使用的是 lambert(兰伯特,或是half lambert半兰伯特) 漫反射模型,计算的是灯光方向与物体表面法线方向的夹角的余弦值。
- 高光 用于模拟物体对射入光按物体表现法线方向做反射出去的光,一般有 phong 高光与 blinn-phong 高光模型。
计算公式,光照模型 :
其中:
- 是环境光,Phong 中这里就一个颜色值, 是环境光强度系数
- 是漫反射颜色值, 是漫反射强度系数, 是lambert 漫反射模型,其中 是物体表面法线单温方向, 是物体表面指向第 个灯光的一个方向,也叫灯光方向,也是个单位向量。
- 是高光颜色, 是高光强度系数, 是灯光方向的反方向,即:灯光入射方向, 求的是入射方向在根据法线方向反弹的高光反射反向,再用该用 视角方向(从顶点到相机/镜头/观察者的方向),再用反射向量于 视角方向求点积值,最后 是控制点积值的幂次。
上面的公式中,一般有些 Phong 光照是有限制 ,但我也看到过很多例子都不使用这个限制,我们可以在外部控制是否要有这个系数限制:只要 和 都是1的分量系数即可。
这里头值得说明一下的是, 的作用,在以前刚接触 shader 时,我根本不懂这个函数的作用,通过自己试验与网上资料查询后,才有所了解。
dot - 点积的作用
在图形学中, 的作用一定要了解,这里为了完善 LearnGL 系列笔记,我将以前学习理解的 特性、本质才简单的描述一下。
百度百科:点积
这里面我不想太多抄袭其他的专业的公式来表达,我指向表达我在图形学中,什么情况下我会去用 ,这样才能更加理解它的作用。如果你喜欢看公式,你可以点击上面的链接,如果还是不满足,就 google 或是 wiki 中了解。
图形了解顶点点积的作用
如上图,有两个单位向量
和
,它们之前的夹角为
,然后 :
为何也等于 呢?
我们可以在上面说的百度百科中的公司:
如果我们两个都是单位向量 和 ,它意味着向量长度为 1,即:
那么上面的公式就变成:
而 可以理解为初中学的
在下图就可以理解为是 , 就是邻边, 就是斜边,而 ,所以
这个值有什么用呢?
我们通过GIF动态图了解一下这个值的规律:
可以看到:
,点积值1,
,点积值0。
,点积值-1。
通过这两点,可以总结: 可以用于判断两个向量的方向相似程度,越相似值越接近1,垂直为0,越反向值接近-1。
这在我们计算 漫反射 与 高光都会用到:
漫反射
说明,
法线 与 第
个
灯光方向 约相似,那么漫反射就越大
如,上图,
是法线方向,
是灯光方向,如果这两个向量反向越是相似,那么交点
的漫反射值就越大。
如上图GIF,如果我把
拉倒接近
法线,那么
交点就肯定越亮,这个是漫反射的特性,不过这里这么做都是 Phong 的经验模型,真正显示生活中的漫反射是非常复杂的,它是光输入物体内再各种吸收、折射、反射后又从物体内反射出来的光子,非常的复杂,我们只能模拟看起来比较像的效果。
所以漫反射我自己总结是:迎面的光,就是面向越是靠近光的越亮。
高光
是一种类似镜面反射的现象,用一张图表示的话,可以是这样的:
specular 就是镜面反射高光系数,但是 GGB 竟然没有pow,或是 power 次幂函数。
所以高光最终系数公式算法模型:
可以看到也有一个 ,作用与前面的漫反射作用差不多,这里它是求,反射出来的 高光方向与观察者(V,可以理解为眼睛的位置的方向)方向的相似度。意思,越是直接的照射到我们的眼睛的高光系数就越大、高光颜色越亮。
reflect - 反射高光方向
这在我们计算 漫反射 与 高光都会用到: 中的,高光: ,这里不管后面的 pow,它是用来调整光泽度的,glossy 就是光泽度的意思。
高光反向是使用 reflect GLSL 函数来实现的。
reflect 函数的算法是:
画个图会好理解一些:
先是已知:
、
先是 (就是图中的L)
然后是
,但这里先不将它,我们先将后面的
,还记得它前面说的么?
就是求
量向量的相似度,但这里不是这么理解来使用的,它是当作
来使用的,它求的是什么?还是再画个图来理解吧:
求的就是
在
上的投影长度,它也是
,
,因为
都是1,因为都是单位向量,所以
,所以我们
求的是
在
方向投影的长度,这个长度值用来缩放
向量,那么如下图:
这个现在与我们的反射角度差不多了,在加多一个
看看会怎么样?如下图:
所以现在已经求出了反射向量了,只要我们将原点与这个
的点相连就是反射向量了,如下图:
然后我们的结果是:
,后面部分有相同的相加,调整为乘法:
,那么这个结果就与我们之前的列出的reflect公式一模一样了。
view - 观察方向
求的了 reflect 的高光反射方向,我们就可以使用 R 与 V(View,观察方向)来求相似度,还记得前面重复强调的 dot 是用来求量向量的相似度的吧?这里我们也是使用它来求 R 与 V 的的相似度。
它们越相似,说明反射光越是直接照射到我们眼球看向的方向的反方向。
留意这个值:
先看观察位置不变,只改变灯光方向的Specular 高光值效果,看下面的GIF图:
再看看,只改变观察方向的Specular 高光值效果,看下面的GIF图:
总结就是如我们上面所述的:只要 R 与 V 方向相似度越高,Specular 高光越大。
实践
先看效果图,下面是实时调整平行方向光的 方向,强度 和 灯光颜色
从 Phong 光照模型公司可得知:
,除了有漫反射、高光,还有一个:环境光。
环境光的计算非常简单,只是颜色*强度的结果相加到最终颜色的输出即可,具体可以参考下面的 Shader 代码
Shader - 着色器
这里主要看气球猫的 Shader 吧:
// jave.lin - testing_load_balloon_cat_mesh_shading.vert
#version 450 compatibility// camera uniform
uniform vec3 _CamWorldPos; // 镜头世界坐标// scene uniform
uniform vec4 _Ambient; // .xyz 环境光颜色, .w 环境光系数// object uniform
uniform float Glossy; // 光滑度
uniform vec3 DiffuseK; // 漫反射系数
uniform vec3 SpecularK; // 高光系数// light uniform
uniform vec4 LightPos; // 灯光世界坐标位置,w==0,或名是方向光,w==1说明是点光源,w == 0.5 是聚光灯
uniform vec4 LightColor; // 灯光颜色,.xyz 顔色,.w 强度
// uniform vec3 LightDir; // 灯光类型为聚光灯的方向// transform matrix uniform
uniform mat4 mMat; // m.v.p 矩阵
uniform mat4 vMat;
uniform mat4 pMat;
uniform mat4 I_mMat; // model matrix 的逆矩阵// vertex data
attribute vec3 vPos; // 顶点坐标
attribute vec2 vUV0; // 顶点纹理坐标
attribute vec3 vNormal; // 顶点法线// vertex data - interpolation
varying vec2 fUV0; // 给 fragment shader 传入的插值
varying vec3 fAmbient; // 环境光
varying vec3 fDiffuse; // 漫反射颜色
varying vec3 fSpecular; // 高光颜色// 将对象空间的法线转换到世界空间下的法线
vec3 ObjectToWorldNormal(vec3 n) {n = (vec4(n,0) * I_mMat).xyz; // 等价于:transpose(I_mMat) * vec4(n, 0)return normalize(n);
}void main() {vec4 h_vPos = vec4(vPos, 1.0); // 齐次坐标vec4 worldPos = mMat * h_vPos; // 世界坐标vec3 viewDir = normalize(_CamWorldPos - worldPos.xyz); // 顶点坐标 指向 镜头坐标 的方向vec3 worldNormal = ObjectToWorldNormal(vNormal); // 获取世界坐标下的法线// phong shading// ambientfAmbient = _Ambient.xyz * _Ambient.w;if (LightPos.w == 0) {// 下面使用的是Phong 光照模型// 如果是方向光,那么 LightPos.xyz 是灯光方向的反方向fDiffuse = LightColor.rgb * LightColor.w * DiffuseK * max(0, dot(LightPos.xyz, worldNormal)); // lambert// diffuse = LightColor * DiffuseK * max(0, dot(LightPos.xyz, worldNormal)) * 0.5 + 0.5; // half-lambert : -1~1 to 0~1fSpecular = max(vec3(0, 0, 0), fDiffuse) * LightColor.rgb * LightColor.w * SpecularK * pow(dot(reflect(LightPos.xyz, worldNormal), viewDir), Glossy);} else {// 点光源 或是 聚光灯if (LightPos.w == 1) {// 点光} else { // LightPos.w == 0.5,即:LightPos.w !=0 && LightPos.w != 1// 聚光灯}}// uv0fUV0 = vUV0;gl_Position = pMat * vMat * worldPos;
}// jave.lin - testing_load_balloon_cat_mesh_shading.frag
#version 450 compatibility// interpolation - 插值数据
varying vec2 fUV0; // uv 坐标
varying vec3 fAmbient; // 环境光
varying vec3 fDiffuse; // 漫反射颜色
varying vec3 fSpecular; // 高光颜色// local uniform
uniform sampler2D main_tex; // 主纹理void main() {vec3 mainCol = texture(main_tex, fUV0).rgb;gl_FragColor = vec4(fAmbient + mainCol * fDiffuse + fSpecular, 1.0);
}
网格模型
这里的为了更好的观察光照效果,我就提前先将网格模型加载提前完整。
使用的是我之前在实现 用C# Bitmap作为画布写个3D软渲染器 的模型来作为练习用,分别是:球体、气球猫。
这两个模型我是从 Unity 资源中导出来的,在 Unity 里写了 CSharp 脚本,将Mesh的Vertex, Indices,UV, Color, Normal, Tangent导出到一个 *.m
的自定义文件,这个 m 可以理解为:model ,模型的意思,我把它放到了 github 上,分别是:
- BalloonStupidCat_637003750150312129.m - Unity Demo 的 气球猫
- Sphere_637003627289014299.m - Unity 的 primitive type mode - sphere 球体
模型*.m
格式目前只支持三角形,够用就行,文件格式粗略说明:
- 纯文本内容
- 每个数据段会以:
#label:count
的格式来说明后续的数据意义label
是数据标签名字,可以是:vertices
顶点数据indices
索引值colors
顶点颜色uv
纹理坐标normals
顶点法线tangents
顶点切线
count
表示的就是该数据段有多少数量
例如,我手写一个三角形模型的结构如下:
(下面的切线我就没有归一化,通常我们加载后最后每个向量都归一化处理)
#vertices:3
-0.5,-0.5,0.0
0.5,-0.5,0.0
0.0,0.5,0.0
#indices:3
0,1,2
#colors:3
1.0,0.0,0.0,1.0
0.0,1.0,0.0,1.0
0.0,0.0,1.0,1.0
#uv:3
0.0,0.0
1.0,0.0
0.5,1.0
#normals:3
0.0,0.0,1.0
0.0,0.0,1.0
0.0,0.0,1.0
#tangents:3
0.0,1.0,0.0
-0.5,1.0,0.0
-0.5,-1.0,0.0
网格模型的读取:在光栅化渲染器中的 CSharp 代码有,在静态类 ModelReader
的 public static void ReadOut(string file, out Mesh mesh)
可以看到是如何读取网格的。
在 LearnGL 中,读取 *.m
网格模型的函数,我也写了个 C++ 版本的。
References
- 颜色
- 基础光照