OpenGL中的光照

Android OpenGLES2.0中的光照

翻译自Android课程二:环境光和漫反射光

1.什么是光?

世界没有光就会什么也看不到,我们将无法感知这个世界活着物体。

在真实的世界里,光是由无数的光子聚合在一起形成的,它从一个光源出发,经过无数时间,最后传递到了我们的眼睛里

我们应该如何用计算机图像模拟光呢,现在有2种热门技术可以实现:光线跟踪和栅格化。光线跟踪通过数学计算跟踪真实的光线,能给出十分准确和真实的结果,但是不足之处是模拟所有的光线十分消耗计算资源,并且实时渲染速度很慢。由于这个限制,大多数的实时计算机图像使用的是栅格化技术,通过接近结果模拟光线。

2.光线的分类

我们可以抽象出光工作的方式形成三种基本的光线类型

  • 环境光

环境光是一种基础的光线,它遍布整个场景,它没有表现出来自任何其他的光源,因为它在到达你之前经过了无数的反射。这种光能阴天的户外,或是许多不同光源累积影响的室内。没有分别计算所有独立的光,我们可以为对象或场景设置一个基本的光照等级。

  • 漫反射光

这种光在到达教你的眼睛里经过了物体之间反射。物体的光照等级随它与光线的角度不同而变化。直面它的时候更加明亮。同样我们感知物体时无论我们相对物体的位置在哪里,亮点都是相同的。这种现象也叫兰伯特余弦定律(Lambert’s cosine law),漫反射和兰伯特反射在生活中是很常见的。

  • 高光

和漫反射不同,高光随我们和物体的位置不同而不同,它让物体发亮和更加光滑

2.模拟光

与在3D场景中的光有3种一样,光源也有3种:直接光源、Point lighting、Spot lighting。

1.数学

学习来自一个点光源的环境光和漫反射光

环境光

环境光其实是漫反射光的一种,但是它也能被看作是充满整个场景的低级光。这样想的话,它会很容易计算

final color = material color * ambient light color

例如,有个物体是红色的,我们的环境光是灰白的。让我们假定将颜色存储为有3个颜色的数组红、绿、蓝,使用RGB 颜色模型:

final color = { 1 , 0 , 0 } * { 0.1 , 0.1 , 0.1 } = { 0.1 , 0.0 , 0.0 }

最终物体的颜色会是淡红色。基础的场景光就是这样的,除非你想要更加高级的视觉技术

漫反射光 - 点光源

对漫反射光,我们需要增加衰减和光的位置。光的位置会用来计算光和表面的角度,它会影响表面的光照的整个等级。它还用来计算光和表面的距离,并决定了那个点的光照强度。

步骤一: 计算兰伯特因子

我们需要的第一个主要的计算是计算出表明和光的角度。直面光线的表层会处于光照的最大强度。计算出这个属性的合适的方式是使用兰伯特余弦定理。如果我们有2个向量,一个是从光线到表面的一个点,第二个是曲面法线,我们可以计算出余弦值:先将各个向量归一化,因此有它们的长度为1,然后计算出2个向量的点积。这个操作能通过OpenGL ES的两个着色器轻松完成。

我们声明lambert因子,它的范围是0到1

1.

light vector = light position - obejct position
cosine = dot product(object normal,normalize(light vector))
lambert factor = max(cosine,0)

首先我们通过对象位置减去光线位置计算出光线的向量,然后我们获取物体的法线和光线向量的点积,就得到了这个余弦了。归一化光线向量的意思就是改变它的长度为1。因为点积德范围是-1到1,我们把它固定到(0,1)。

这是一个例子:有个光滑的平面,表面的法线笔直的指向天。光线的位置为「0,10,-10」,或是向上10个单位,向前10个单位,我们要计算出原地的光

light vector = { 0, 10, -10} - {0, 0, 0} = {0, 10, -10}
obejct normal = {0, 1, 0}

用平白的语言来说,如果我们沿着光线向量出发移动,到达光线的位置。要归一化这个向量,我们让向量的每一个标量处以向量的长度:

light vector length = square root( 0*0 + 10*10 + -10*-10) = square root(200) = 14.14
normalized light vector = {0/14.14, 10/14.14, -10/14.14} = {0, 0.707, -0.707}

然后我们计算点积

dot product({0,1,0},{0,0.707, -0.707}) = 0*0 + 1*0.707 + 0*-0.707 = 0.707
lambert factor = max(0.707,0) = 0.707

OpenGL ES 2的着色器语言已经内置了一些这种函数,所以我们不需要手动做所有的数学,但是这对我们的理解仍然有帮助。

步骤二:计算衰减因子

接着,我们需要计算衰减,真实的点光源的光线衰减遵从平方反比定律,它可以表述为:
luminosity = 1 / (distance* distance)
回到我们的例子,我们知道了一个距离为14.14,所以最后我们的亮度看起来是:
luminosity = 1/ (14.14*14.14) = 0.005
你可以看到,平方反比定律在距离上会导致剧烈的衰减。这就是光在真实世界中从点光源出发是怎么回事,但是我们的图像显示有限制范围,因此抑制衰减因子,我们能得到更加真实的光照而不会使物体看起来太暗了。

步骤三:计算最后的颜色

现在我们同时有了余弦和衰减因子,我们能计算出最终的光照等级:

final color = meterial color * (light color * lambert factor * luminosity)

回到之前一个例子,我们有红色的材料和全白的光源,最终的计算:

final color = {1, 0, 0} *({1,1,1}*0.707*0.005) 
= {1,0,0}*{0.0035,0.0035,0.0035} 
= {0.0035,0,0}

简单的总结一下,对漫反射光我们需要使用表面和光线的角度,还有表面和光线的距离来最终计算出整个漫反射照明等级。下面是步骤:

//one
light vector = light position - object position
cosine = dot product(object normal,normalize(light vector))
lambert factor = max(cosine,0)

//two
luminosity = 1/(distance*distance)

//three
final color = material color * (light color*lambert factor * luminosity)

顶点着色器

final String vertexShader =
    "uniform mat4 u_MVPMatrix;      \n"     // 一个表示进行了Model变换、View变换、投影变换的矩阵
  + "uniform mat4 u_MVMatrix;       \n"     // 一个表示进行了Model变换和View变换的矩阵
  + "uniform vec3 u_LightPos;       \n"     // 在眼坐标系中光的位置

  + "attribute vec4 a_Position;     \n"     // 传入的顶点位置信息
  + "attribute vec4 a_Color;        \n"     // 传人的每个顶点的颜色信息
  + "attribute vec3 a_Normal;       \n"     // 每个顶点的法线信息

  + "varying vec4 v_Color;          \n"     // 颜色信息,这个变量会传递进片段着色器中

  + "void main()                    \n"     
  + "{                              \n"
// 将顶点转化为眼坐标系
  + "   vec3 modelViewVertex = vec3(u_MVMatrix * a_Position);              \n"
// 将法线方向转移进眼坐标系
  + "   vec3 modelViewNormal = vec3(u_MVMatrix * vec4(a_Normal, 0.0));     \n"
// 计算出顶点和光线的距离  
  + "   float distance = length(u_LightPos - modelViewVertex);             \n"
// 获取从光线处到顶点的光线向量
  + "   vec3 lightVector = normalize(u_LightPos - modelViewVertex);        \n"
// 计算lambert factor,也就是计算光线向量和顶点法线的点积. 如果法线向量和光线向量指向同一个方向,则会得出最大的光照强度.
  + "   float lambert = max(dot(modelViewNormal, lightVector), 0.1);       \n"
// 光线随距离的衰减
  + "   float diffuse = lambert * (1.0 / (1.0 + (0.25 * distance * distance)));  \n"
// 将颜色乘以衰减度, It will be interpolated across the triangle.
  + "   v_Color = a_Color * diffuse;                                       \n"
// gl_Position 存储最终的位置.
// 将这个向量乘以变换矩阵得到在归一屏幕坐标系中的点
  + "   gl_Position = u_MVPMatrix * a_Position;                            \n"
  + "}                                                                     \n";

片段着色器

final String fragmentShader =
  "precision mediump float;       \n"     // Set the default precision to medium. We don't need as high of a
                                          // precision in the fragment shader.
+ "varying vec4 v_Color;          \n"     // This is the color from the vertex shader interpolated across the
                                          // triangle per fragment.
+ "void main()                    \n"     // The entry point for our fragment shader.
+ "{                              \n"
+ "   gl_FragColor = v_Color;     \n"     // Pass the color directly through the pipeline.
+ "}                              \n";

光源的顶点、片段着色器

// Define a simple shader program for our point.
final String pointVertexShader =
    "uniform mat4 u_MVPMatrix;      \n"
  + "attribute vec4 a_Position;     \n"
  + "void main()                    \n"
  + "{                              \n"
  + "   gl_Position = u_MVPMatrix   \n"
  + "               * a_Position;   \n"
  + "   gl_PointSize = 5.0;         \n"
  + "}                              \n";

final String pointFragmentShader =
    "precision mediump float;       \n"
  + "void main()                    \n"
  + "{                              \n"
  + "   gl_FragColor = vec4(1.0,    \n" //直接指定片段颜色为白色
  + "   1.0, 1.0, 1.0);             \n"
  + "}                              \n";

有一个新的属性叫gl_PointSize是点在空间中的大小为多少个像素。

猜你喜欢

转载自blog.csdn.net/u013648164/article/details/74936530