OpenGL进阶(十三) - GLSL光照(Lighting)

本文详细介绍了光线追踪技术中的方向光源、聚光灯、半向量优化、逐像素着色等概念,并通过实例如何在场景中应用这些光源模型,展示了不同光源对模型表面光照效果的影响。同时,引入了性能优化技巧,如使用半向量来简化计算过程,以及聚光灯模型的实现,进一步丰富了光照效果的表现。通过逐像素着色,实现了更加真实细腻的渲染效果,展现了光线与物体表面交互的复杂性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

提要

       在上一篇文章中,我们介绍了简单的Shading,同时提出了一个光照模型,模拟了一个点光源,但是,关于光的故事还没有结束... 

       今天要学习的是方向光源(Directional Light),聚光灯,per pixel shading,halfway vector。

      关于光源的原理及数学描述,请参考:光线追踪(RayTracing)算法理论与实践(三)光照


方向光源

      方向光源就两个参数,方向和强度。

      还是简单的 ambient + diffuse + spec 光照模型。先看shader的代码。

basic.vert

  1. #version 400   
  2. layout (location = 0) in vec3 VertexPosition;    
  3. layout (location = 1) in vec3 VertexNormal;    
  4.   
  5. out vec3 LightIntensity;  
  6.   
  7. struct LightInfo{  
  8.     vec4 Direction;  
  9.     vec3 Intensity;  
  10. };  
  11.   
  12. struct MaterialInfo{  
  13.     vec3 Ka;  
  14.     vec3 Kd;  
  15.     vec3 Ks;  
  16.     float Shininess;  
  17. };  
  18.   
  19. uniform LightInfo Light;  
  20. uniform MaterialInfo Material;  
  21.   
  22. uniform mat4 ModelViewMatrix;  
  23. uniform mat3 NormalMatrix;  
  24. uniform mat4 ProjectionMatrix;  
  25. uniform mat4 MVP;  
  26.   
  27.   
  28. void getEyeSpace(out vec3 norm, out vec4 position)  
  29. {  
  30.     norm =  normalize(NormalMatrix * VertexNormal);  
  31.     position = ModelViewMatrix * vec4(VertexPosition, 1.0);  
  32. }  
  33.   
  34.   
  35.   
  36. vec3 ads(vec4 position, vec3 norm)  
  37. {  
  38.     vec3 s;  
  39.     if(Light.Direction.w == 0.0)  
  40.         s = normalize(vec3(Light.Direction));  
  41.     else  
  42.         s = normalize(vec3(Light.Direction - position));  
  43.     vec3 v = normalize(vec3(-position));  
  44.     vec3 r = reflect(-s, norm);  
  45.   
  46.     return Light.Intensity * (Material.Ka + Material.Kd*max(dot(s,norm), 0.0) +   
  47.            Material.Ks * pow(max(dot(r,v),0.0), Material.Shininess));  
  48. }  
  49.   
  50. void main()  
  51. {  
  52.     vec3 eyeNorm;  
  53.     vec4 eyePosition;  
  54.     getEyeSpace(eyeNorm, eyePosition);  
  55.     LightIntensity = ads(eyePosition, eyeNorm);  
  56.       
  57.     gl_Position = MVP * vec4( VertexPosition, 1.0);  
  58. }  
#version 400
layout (location = 0) in vec3 VertexPosition;  
layout (location = 1) in vec3 VertexNormal;  

out vec3 LightIntensity;

struct LightInfo{
	vec4 Direction;
	vec3 Intensity;
};

struct MaterialInfo{
	vec3 Ka;
	vec3 Kd;
	vec3 Ks;
	float Shininess;
};

uniform LightInfo Light;
uniform	MaterialInfo Material;

uniform mat4 ModelViewMatrix;
uniform mat3 NormalMatrix;
uniform mat4 ProjectionMatrix;
uniform mat4 MVP;


void getEyeSpace(out vec3 norm, out vec4 position)
{
	norm =  normalize(NormalMatrix * VertexNormal);
	position = ModelViewMatrix * vec4(VertexPosition, 1.0);
}



vec3 ads(vec4 position, vec3 norm)
{
	vec3 s;
	if(Light.Direction.w == 0.0)
		s = normalize(vec3(Light.Direction));
	else
		s = normalize(vec3(Light.Direction - position));
	vec3 v = normalize(vec3(-position));
	vec3 r = reflect(-s, norm);

	return Light.Intensity * (Material.Ka + Material.Kd*max(dot(s,norm), 0.0) + 
	       Material.Ks * pow(max(dot(r,v),0.0), Material.Shininess));
}

void main()
{
	vec3 eyeNorm;
	vec4 eyePosition;
	getEyeSpace(eyeNorm, eyePosition);
	LightIntensity = ads(eyePosition, eyeNorm);
	
	gl_Position = MVP * vec4( VertexPosition, 1.0);
}

在ads函数中,首先通过nomal矩阵将顶点法向量变换到视口坐标下,(nomal矩阵其实就是model-view矩阵的左上3x3的矩阵)然后通过model-view矩阵将顶点坐标转化为视口坐标系(eye coordinates)下。

接下来的ads用来计算光照模型下顶点的颜色,分别计算三个分量,然后相加。


basic.frag

  1. #version 400   
  2.   
  3. in vec3 LightIntensity;  
  4.   
  5. void main(void)  
  6. {  
  7.     gl_FragColor = vec4(LightIntensity, 1.0);  
  8.     //gl_FragColor = vec4(1.0,1.0,0.1, 1.0);   
  9. }  
#version 400

in vec3 LightIntensity;

void main(void)
{
	gl_FragColor = vec4(LightIntensity, 1.0);
	//gl_FragColor = vec4(1.0,1.0,0.1, 1.0);
}
这个就是将根据顶点shader传来的颜色对片段进行赋值。

在cgl.cpp的setUniform函数中对Uniform变量进行赋值。
  1. void CGL::setUniform()  
  2. {  
  3.     mat4 projection = glm::perspective(45.0f, 4.0f / 3.0f, 0.1f, 100.0f);  
  4.     mat4 mv = view * model;  
  5.   
  6.     prog.setUniform("Material.Kd", 0.9f, 0.5f, 0.3f);  
  7.     prog.setUniform("Material.Ka", 0.9f, 0.5f, 0.3f);  
  8.     prog.setUniform("Material.Ks", 0.8f, 0.8f, 0.8f);  
  9.     prog.setUniform("Material.Shininess", 100.0f);  
  10.     prog.setUniform("Light.Direction", vec4(1.0f, 0.0f, 0.0f, 0.0f));  
  11.     prog.setUniform("Light.Intensity", 1.0f, 1.0f, 1.0f);  
  12.   
  13.     prog.setUniform("ModelViewMatrix", mv);  
  14.     prog.setUniform("NormalMatrix",mat3( vec3(mv[0]), vec3(mv[1]), vec3(mv[2]) ));  
  15.     prog.setUniform("MVP", projection * mv);  
  16.   
  17. }  
void CGL::setUniform()
{
    mat4 projection = glm::perspective(45.0f, 4.0f / 3.0f, 0.1f, 100.0f);
    mat4 mv = view * model;

    prog.setUniform("Material.Kd", 0.9f, 0.5f, 0.3f);
    prog.setUniform("Material.Ka", 0.9f, 0.5f, 0.3f);
    prog.setUniform("Material.Ks", 0.8f, 0.8f, 0.8f);
    prog.setUniform("Material.Shininess", 100.0f);
    prog.setUniform("Light.Direction", vec4(1.0f, 0.0f, 0.0f, 0.0f));
    prog.setUniform("Light.Intensity", 1.0f, 1.0f, 1.0f);

    prog.setUniform("ModelViewMatrix", mv);
    prog.setUniform("NormalMatrix",mat3( vec3(mv[0]), vec3(mv[1]), vec3(mv[2]) ));
    prog.setUniform("MVP", projection * mv);

}

渲染的效果如下:



可以很明显的感觉模型旋转时到表面光照的变化。


halfway vector 性能优化

        在前面的光照模型中,用于计算specular分量的公式如下:


其中 是反射光线的方向向量,v是往视口方向的向量,其中 r 的计算:


这个计算过程会非常耗时,我们可以用一个trick来改善一下。

定义一个 h (halfway vector)向量:


下图是 和其他向量的位置关系。


specular分量的计算就可以转化成:


         相比于计算 r ,的计算相对比较简单,而 h 和 n 之间的夹角与 和 r 之间的夹角大小几乎相同!那就意味着我们可以用 h.n 来代替 r.v 从而可以带利用 halfway vector 来获得性能上的一些提升。虽然效果上会有那么小小的不同。

         后面的灯光的计算都会用到这个优化。

聚光灯 Spotlight

        这里的采用一个最简单的聚光灯模型:



       灯光的属性有:位置,强度,方向,衰减(exponent),裁剪角度。

       实现起来也比较简单,在投射角内的物体,渲染方式和点光源的计算一样,投射角之外的顶点,着色的时候只有ambient。

        还是采用我们比较熟悉的per vertex shading 方式。在vert中定义聚光灯:

  1. //baisc.vert   
  2. #version 400   
  3. layout (location = 0) in vec3 VertexPosition;    
  4. layout (location = 1) in vec3 VertexNormal;    
  5.   
  6. out vec3 LightIntensity;  
  7.   
  8. struct SpotLightInfo{  
  9.     vec4 position;  
  10.     vec3 direction;  
  11.     vec3 intensity;  
  12.     float exponent;  
  13.     float cutoff;  
  14. };  
  15.   
  16. struct MaterialInfo{  
  17.     vec3 Ka;  
  18.     vec3 Kd;  
  19.     vec3 Ks;  
  20.     float Shininess;  
  21. };  
  22.   
  23. uniform SpotLightInfo Spot;  
  24. uniform MaterialInfo Material;  
  25.   
  26. uniform mat4 ModelViewMatrix;  
  27. uniform mat3 NormalMatrix;  
  28. uniform mat4 ProjectionMatrix;  
  29. uniform mat4 MVP;  
  30.   
  31.   
  32. void getEyeSpace(out vec3 norm, out vec4 position)  
  33. {  
  34.     norm =  normalize(NormalMatrix * VertexNormal);  
  35.     position = ModelViewMatrix * vec4(VertexPosition, 1.0);  
  36. }  
  37.   
  38.   
  39.   
  40. vec3 adsSpotLight(vec4 position, vec3 norm)  
  41. {  
  42.     vec3 s = normalize(vec3(Spot.position - position));  
  43.     float angle = acos(dot(-s, normalize(Spot.direction)));  
  44.     float cutoff = radians(clamp(Spot.cutoff, 0.0, 90.0));  
  45.     vec3 ambient = Spot.intensity * Material.Ka;  
  46.       
  47.     if(angle < cutoff){  
  48.         float spotFactor = pow(dot(-s, normalize(Spot.direction)), Spot.exponent);  
  49.         vec3 v = normalize(vec3(-position));  
  50.         vec3 h = normalize(v + s);  
  51.         return ambient + spotFactor * Spot.intensity * (Material.Kd * max(dot(s, norm),0.0)  
  52.               + Material.Ks * pow(max(dot(h,norm), 0.0),Material.Shininess));   
  53.     }  
  54.     else  
  55.     {  
  56.         return ambient;   
  57.     }  
  58.              
  59. }  
  60.   
  61. void main()  
  62. {  
  63.     vec3 eyeNorm;  
  64.     vec4 eyePosition;  
  65.     getEyeSpace(eyeNorm, eyePosition);  
  66.       
  67.     LightIntensity = adsSpotLight(eyePosition, eyeNorm);  
  68.       
  69.     gl_Position = MVP * vec4( VertexPosition, 1.0);  
  70. }  
//baisc.vert
#version 400
layout (location = 0) in vec3 VertexPosition;  
layout (location = 1) in vec3 VertexNormal;  

out vec3 LightIntensity;

struct SpotLightInfo{
	vec4 position;
	vec3 direction;
	vec3 intensity;
	float exponent;
	float cutoff;
};

struct MaterialInfo{
	vec3 Ka;
	vec3 Kd;
	vec3 Ks;
	float Shininess;
};

uniform SpotLightInfo Spot;
uniform	MaterialInfo Material;

uniform mat4 ModelViewMatrix;
uniform mat3 NormalMatrix;
uniform mat4 ProjectionMatrix;
uniform mat4 MVP;


void getEyeSpace(out vec3 norm, out vec4 position)
{
	norm =  normalize(NormalMatrix * VertexNormal);
	position = ModelViewMatrix * vec4(VertexPosition, 1.0);
}



vec3 adsSpotLight(vec4 position, vec3 norm)
{
	vec3 s = normalize(vec3(Spot.position - position));
	float angle = acos(dot(-s, normalize(Spot.direction)));
	float cutoff = radians(clamp(Spot.cutoff, 0.0, 90.0));
	vec3 ambient = Spot.intensity * Material.Ka;
	
	if(angle < cutoff){
		float spotFactor = pow(dot(-s, normalize(Spot.direction)), Spot.exponent);
		vec3 v = normalize(vec3(-position));
		vec3 h = normalize(v + s);
		return ambient + spotFactor * Spot.intensity * (Material.Kd * max(dot(s, norm),0.0)
		      + Material.Ks * pow(max(dot(h,norm), 0.0),Material.Shininess)); 
	}
	else
	{
		return ambient; 
	}
	       
}

void main()
{
	vec3 eyeNorm;
	vec4 eyePosition;
	getEyeSpace(eyeNorm, eyePosition);
	
	LightIntensity = adsSpotLight(eyePosition, eyeNorm);
	
	gl_Position = MVP * vec4( VertexPosition, 1.0);
}

几个GLSL中的内置函数在这里说明一下。

genType clamp( genType x, genType minVal, genType maxVal);

获取三个数中第二大的数。

genType radians(genType degrees);

将角度转换成弧度。

adsSpotLight是主要的函数,先计算顶点和光源方向之间的夹角,判断顶点是否在照射的区域,然后分别求得最终的颜色。


片段shader还是那样:

  1. #version 400   
  2.   
  3. in vec3 LightIntensity;  
  4.   
  5. void main(void)  
  6. {  
  7.     gl_FragColor = vec4(LightIntensity, 1.0);  
  8. }  
#version 400

in vec3 LightIntensity;

void main(void)
{
	gl_FragColor = vec4(LightIntensity, 1.0);
}

uniform变量的赋值:

  1. void CGL::setUniform()  
  2. {  
  3.     //model = glm::rotate(this->model, 10.0f, vec3(0.0f,1.0f,0.0f));   
  4.     mat4 projection = glm::perspective(45.0f, 4.0f / 3.0f, 0.1f, 100.0f);  
  5.     mat4 mv = view * model;  
  6.     mat3 normalMatrix = mat3( vec3(view[0]), vec3(view[1]), vec3(view[2]) );  
  7.   
  8.     prog.setUniform("Material.Kd", 0.9f, 0.5f, 0.3f);  
  9.     prog.setUniform("Material.Ka", 0.9f, 0.5f, 0.3f);  
  10.     prog.setUniform("Material.Ks", 0.8f, 0.8f, 0.8f);  
  11.     prog.setUniform("Material.Shininess", 100.0f);  
  12.   
  13.     vec4 lightPos = vec4(1.0f, 5.0f, 20.0f, 1.0f);  
  14.    // prog.setUniform("Spot.position", lightPos);   
  15.     prog.setUniform("Spot.position", view * lightPos);  
  16.     prog.setUniform("Spot.direction", normalMatrix * vec3(-10.0,0.0,-40.0) );  
  17.     //prog.setUniform("Spot.direction", vec3(10.9f,10.9f,10.9f)  );   
  18.     prog.setUniform("Spot.intensity", vec3(0.9f,0.9f,0.9f) );  
  19.     prog.setUniform("Spot.exponent", 30.0f );  
  20.     prog.setUniform("Spot.cutoff", 15.0f );  
  21.   
  22.     prog.setUniform("ModelViewMatrix", mv);  
  23.     prog.setUniform("NormalMatrix",normalMatrix);  
  24.     prog.setUniform("MVP", projection * mv);  
  25.   
  26. }  
void CGL::setUniform()
{
    //model = glm::rotate(this->model, 10.0f, vec3(0.0f,1.0f,0.0f));
    mat4 projection = glm::perspective(45.0f, 4.0f / 3.0f, 0.1f, 100.0f);
    mat4 mv = view * model;
    mat3 normalMatrix = mat3( vec3(view[0]), vec3(view[1]), vec3(view[2]) );

    prog.setUniform("Material.Kd", 0.9f, 0.5f, 0.3f);
    prog.setUniform("Material.Ka", 0.9f, 0.5f, 0.3f);
    prog.setUniform("Material.Ks", 0.8f, 0.8f, 0.8f);
    prog.setUniform("Material.Shininess", 100.0f);

    vec4 lightPos = vec4(1.0f, 5.0f, 20.0f, 1.0f);
   // prog.setUniform("Spot.position", lightPos);
    prog.setUniform("Spot.position", view * lightPos);
    prog.setUniform("Spot.direction", normalMatrix * vec3(-10.0,0.0,-40.0) );
    //prog.setUniform("Spot.direction", vec3(10.9f,10.9f,10.9f)  );
    prog.setUniform("Spot.intensity", vec3(0.9f,0.9f,0.9f) );
    prog.setUniform("Spot.exponent", 30.0f );
    prog.setUniform("Spot.cutoff", 15.0f );

    prog.setUniform("ModelViewMatrix", mv);
    prog.setUniform("NormalMatrix",normalMatrix);
    prog.setUniform("MVP", projection * mv);

}

       在这里给Spot.position赋值的时候不是 prog.setUniform("Spot.position", lightPos); 而是prog.setUniform("Spot.position", view * lightPos),因为在shader中的计算都是在视口坐标下进行的,这样做是为了统一坐标。Spot.direction的赋值也是一样。也可以把坐标转换这一步放到shader中去做。

最终效果如下:



逐像素着色 per pixel shading

        相对与前面将主要计算工作放在顶点shader中的 per vertex shading ,per pixel shading 指的是将计算放到片段shader中,这样可以带来更加真实可感的渲染效果。

basic2.vert

  1. #version 400   
  2. layout (location = 0) in vec3 VertexPosition;    
  3. layout (location = 1) in vec3 VertexNormal;    
  4.   
  5. out vec4 Position;  
  6. out vec3 Normal;  
  7.   
  8.   
  9. uniform mat4 ModelViewMatrix;  
  10. uniform mat3 NormalMatrix;  
  11. uniform mat4 ProjectionMatrix;  
  12. uniform mat4 MVP;  
  13.   
  14.   
  15. void getEyeSpace(out vec3 norm, out vec4 position)  
  16. {  
  17.     norm =  normalize(NormalMatrix * VertexNormal);  
  18.     position = ModelViewMatrix * vec4(VertexPosition, 1.0);  
  19. }  
  20.   
  21.   
  22. void main()  
  23. {  
  24.     getEyeSpace(Normal, Position);  
  25.     gl_Position = MVP * vec4( VertexPosition, 1.0);  
  26. }  
#version 400
layout (location = 0) in vec3 VertexPosition;  
layout (location = 1) in vec3 VertexNormal;  

out vec4 Position;
out vec3 Normal;


uniform mat4 ModelViewMatrix;
uniform mat3 NormalMatrix;
uniform mat4 ProjectionMatrix;
uniform mat4 MVP;


void getEyeSpace(out vec3 norm, out vec4 position)
{
	norm =  normalize(NormalMatrix * VertexNormal);
	position = ModelViewMatrix * vec4(VertexPosition, 1.0);
}


void main()
{
	getEyeSpace(Normal, Position);
	gl_Position = MVP * vec4( VertexPosition, 1.0);
}

basic2.frag 

  1. #version 400   
  2.   
  3. in vec4 Position;  
  4. in vec3 Normal;  
  5.   
  6. struct SpotLightInfo{  
  7.     vec4 position;  
  8.     vec3 direction;  
  9.     vec3 intensity;  
  10.     float exponent;  
  11.     float cutoff;  
  12. };  
  13.   
  14. struct MaterialInfo{  
  15.     vec3 Ka;  
  16.     vec3 Kd;  
  17.     vec3 Ks;  
  18.     float Shininess;  
  19. };  
  20.   
  21. uniform SpotLightInfo Spot;  
  22. uniform MaterialInfo Material;  
  23.   
  24. vec3 adsSpotLight(vec4 position, vec3 norm)  
  25. {  
  26.     vec3 s = normalize(vec3(Spot.position - position));  
  27.     float angle = acos(dot(-s, normalize(Spot.direction)));  
  28.     float cutoff = radians(clamp(Spot.cutoff, 0.0, 90.0));  
  29.     vec3 ambient = Spot.intensity * Material.Ka;  
  30.       
  31.     if(angle < cutoff){  
  32.         float spotFactor = pow(dot(-s, normalize(Spot.direction)), Spot.exponent);  
  33.         vec3 v = normalize(vec3(-position));  
  34.         vec3 h = normalize(v + s);  
  35.         return ambient + spotFactor * Spot.intensity * (Material.Kd * max(dot(s, norm),0.0)  
  36.               + Material.Ks * pow(max(dot(h,norm), 0.0),Material.Shininess));   
  37.     }  
  38.     else  
  39.     {  
  40.         return ambient;   
  41.     }  
  42.              
  43. }  
  44.   
  45. void main(void)  
  46. {  
  47.     gl_FragColor = vec4(adsSpotLight(Position, Normal), 1.0);  
  48.     //gl_FragColor = vec4(1.0,1.0,0.5, 1.0);   
  49. }  
#version 400

in vec4 Position;
in vec3 Normal;

struct SpotLightInfo{
	vec4 position;
	vec3 direction;
	vec3 intensity;
	float exponent;
	float cutoff;
};

struct MaterialInfo{
	vec3 Ka;
	vec3 Kd;
	vec3 Ks;
	float Shininess;
};

uniform SpotLightInfo Spot;
uniform	MaterialInfo Material;

vec3 adsSpotLight(vec4 position, vec3 norm)
{
	vec3 s = normalize(vec3(Spot.position - position));
	float angle = acos(dot(-s, normalize(Spot.direction)));
	float cutoff = radians(clamp(Spot.cutoff, 0.0, 90.0));
	vec3 ambient = Spot.intensity * Material.Ka;
	
	if(angle < cutoff){
		float spotFactor = pow(dot(-s, normalize(Spot.direction)), Spot.exponent);
		vec3 v = normalize(vec3(-position));
		vec3 h = normalize(v + s);
		return ambient + spotFactor * Spot.intensity * (Material.Kd * max(dot(s, norm),0.0)
		      + Material.Ks * pow(max(dot(h,norm), 0.0),Material.Shininess)); 
	}
	else
	{
		return ambient; 
	}
	       
}

void main(void)
{
	gl_FragColor = vec4(adsSpotLight(Position, Normal), 1.0);
	//gl_FragColor = vec4(1.0,1.0,0.5, 1.0);
}

看一下渲染效果:


最终的效果还是有一些差别,特别是光线交界的地方。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值