openGL中Phong 着色

openGL系列文章目录

前言

Bui Tuong Phong 在犹他大学的研究生期间开发了一种平滑的着色算法,在1973 年的论文[PH73]中对其进行了描述,并在[PH75]中发表。该算法的结构类似于Gouraud 着色的算法,其不同之处在于光照计算是按像素而非顶点完成。由于光照计算需要法向量N 和光向量L,但在模型中仅顶点包含这些信息,因此Phong 着色通常使用巧妙的“技巧”来实现,其中N和L 在顶点着色器中进行计算,并在光栅化期间插值。图1概述了此策略。
openGL中Phong 着色
图1
C++/OpenGL 代码完全如前。之前部分在顶点着色器中完成的过程现在回放入片段着色器中进行。法向量插值的效果如图2 所示。现在我们已经准备好使用Phong 着色实现位置光照射下的环面了。大多数代码与实现Gouraud 着色的代码相同。由于C++/OpenGL 代码完全没有改变,在此我们只展示修改过的顶点着色器和片段着色器,如图3所示,Phong 着色修正了Gouraud 着色中出现的伪影。
openGL中Phong 着色
图2
openGL中Phong 着色
图3

顶点着色器

#version 430
layout (location=0) in vec3 vertPos;
layout (location=1) in vec3 vertNormal;
out vec3 varyingNormal; // 视觉空间顶点法向量
out vec3 varyingLightDir; // 指向光源的向量
out vec3 varyingVertPos; // 视觉空间中的顶点位置
// 结构体和统一变量与Gouraud 着色相同
. . .
void main(void)
{ // 输出顶点位置、光照方向和法向量到光栅器以进行插值
varyingVertPos=(mv_matrix * vec4(vertPos,1.0)).xyz;
varyingLightDir = light.position - varyingVertPos;
varyingNormal=(norm_matrix * vec4(vertNormal,1.0)).xyz;
gl_Position=proj_matrix * mv_matrix * vec4(vertPos,1.0);
}

片元着色器

#version 430
in vec3 varyingNormal;
in vec3 varyingLightDir;
in vec3 varyingVertPos;
out vec4 fragColor;
// 结构体和统一变量与Gouraud 着色相同
. . .
void main(void)
{ // 正规化光照向量、法向量、视觉向量
vec3 L = normalize(varyingLightDir);
vec3 N = normalize(varyingNormal);
vec3 V = normalize(-varyingVertPos);
// 计算光照向量基于N 的反射向量
vec3 R = normalize(reflect(-L, N));
// 计算光照与平面法向量间的角度
float cosTheta = dot(L,N);
// 计算视觉向量与反射光向量的角度
float cosPhi = dot(V,R);
// 计算ADS 分量(按像素),并合并以构建输出颜色
```cpp
vec3 ambient = ((globalAmbient * material.ambient) + (light.ambient * material.ambient)).xyz;
vec3 diffuse = light.diffuse.xyz * material.diffuse.xyz * max(cosTheta,0.0);
vec3 specular =
light.specular.xyz * material.specular.xyz * pow(max(cosPhi,0.0), material.shininess);
fragColor = vec4((ambient + diffuse + specular), 1.0);
}

虽然Phong 着色有着比Gouraud 着色更真实的效果,但这是建立在增大性能消耗的基础上的。James Blinn 在1977 年提出了一种对于Phong 着色的优化方法[BL77],被称为Blinn-Phong反射模型。这种优化是基于观察到Phong 着色中消耗最大的计算之一是解出反射向量R。Blinn 发现向量R 在计算过程中并不是必需的——R 只是用来计算角φ 的手段。角φ 的计算可以不用向量R,而通过L 与V 的角平分线向量H 得到。如图4 所示,H 和N 之间的角α 刚好等于1⁄2(φ)。虽然α 与φ 不同,但Blinn 展示了使用α 代替φ 就已经可以获得足够好的结果。角平分线向量可以简单地使用L+V 得到(见图5),之后cos(α)可以通过•) )H N的点积计算。

openGL中Phong 着色
图4
openGL中Phong 着色
图5
这些计算可以在片段着色器中进行,甚至为了性能考虑(经过一些调整)也可以在顶点着色器中进行。图6 展示了使用Blinn-Phong 着色的环面。它在图形质量上几乎与Phong渲染相同,同时节省了大量性能损耗。
openGL中Phong 着色
图6
程序3 中展示了修改后顶点着色器和片段着色器,它们用来将程序7.2 中的Phong 着色示例转换为Blinn-Phong 着色。C++ / OpenGL 代码与之前一样没有变化。
程序3 Blinn-Phong 着色的环面

顶点着色器

#version 430

layout (location = 0) in vec3 vertPos;
layout (location = 1) in vec3 vertNormal;
out vec4 varyingColor;

struct PositionalLight
{	vec4 ambient;
	vec4 diffuse;
	vec4 specular;
	vec3 position;
};
struct Material
{	vec4 ambient;
	vec4 diffuse;
	vec4 specular;
	float shininess;
};

uniform vec4 globalAmbient;
uniform PositionalLight light;
uniform Material material;
uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
uniform mat4 norm_matrix;

void main(void)
{	vec4 color;
	
	// convert vertex position to view space
	vec4 P = mv_matrix * vec4(vertPos,1.0);
	
	// convert normal to view space
	vec3 N = normalize((norm_matrix * vec4(vertNormal,1.0)).xyz);
	
	// calculate view-space light vector (from point to light)
	vec3 L = normalize(light.position - P.xyz);
	
	//  view vector is negative of view space position
	vec3 V = normalize(-P.xyz);
	
	//  R is reflection of -L around the plane defined by N
	vec3 R = reflect(-L,N);

	// ambient, diffuse, and specular contributions
	vec3 ambient =
		((globalAmbient * material.ambient)
		+ (light.ambient * material.ambient)).xyz;
		
	vec3 diffuse =
		light.diffuse.xyz * material.diffuse.xyz
		* max(dot(N,L), 0.0);
		
	vec3 specular =
		pow(max(dot(R,V), 0.0f), material.shininess)
		* material.specular.xyz * light.specular.xyz;

	// send the color output to the fragment shader
	varyingColor = vec4((ambient + diffuse + specular), 1.0);
	
	// send the position to the fragment shader, as before
	gl_Position = proj_matrix * mv_matrix * vec4(vertPos,1.0);
}

片元着色器

#version 430

in vec4 varyingColor;
out vec4 fragColor;

//  uniforms match those in the vertex shader,
//  but aren’t used directly in this fragment shader

struct PositionalLight
{	vec4 ambient;  
	vec4 diffuse;  
	vec4 specular;  
	vec3 position;
};

struct Material
{	vec4 ambient;  
	vec4 diffuse;  
	vec4 specular;  
	float shininess;
};

uniform vec4 globalAmbient;
uniform PositionalLight light;
uniform Material material;
uniform mat4 mv_matrix;	 
uniform mat4 proj_matrix;
uniform mat4 norm_matrix;

//  interpolate lighted color
// (interpolation of gl_Position is automatic)

void main(void)
{	fragColor = varyingColor;
}

openGL中Phong 着色
图7
openGL中Phong 着色

参考

计算机图形学编程 使用OpenGL和C++

上一篇:速战速决 go - go 面向对象: 结构体(定义结构体,声明结构体,初始化结构体,使用结构体,匿名结构体)


下一篇:(机器人学导论--运动学)(一)刚体运动状态的描述