照明设备
完成照明包括计算和组合环境光、漫射光、镜面光和发射光。
顶点
// …
uniform struct p3d_LightSourceParameters
{ vec4 color
; vec4 ambient
; vec4 diffuse
; vec4 specular
; vec4 position
; vec3 spotDirection
; float spotExponent
; float spotCutoff
; float spotCosCutoff
; float constantAttenuation
; float linearAttenuation
; float quadraticAttenuation
; vec3 attenuation
; sampler2DShadow shadowMap
; mat4 shadowViewMatrix
;
} p3d_LightSource[NUMBER_OF_LIGHTS];
// …
对于除环境光之外的每一盏灯,Panda3D都为您提供了顶点着色器和片段着色器都可用的便捷结构。最大的便利是阴影图和阴影视图矩阵,用于将顶点转换为阴影或光空间。
// …
vertexPosition = p3d_ModelViewMatrix * p3d_Vertex;
// …
for (int i = 0; i < p3d_LightSource.length(); ++i) {
vertexInShadowSpaces[i] = p3d_LightSource[i].shadowViewMatrix * vertexPosition;
}
// …
从顶点着色器开始,您需要为场景中的每一盏灯将顶点从视图空间转换并输出到阴影或灯光空间。稍后在片段着色器中您将需要这个来渲染阴影。阴影或光空间是每个坐标相对于光位置的地方(光是原点)。
碎片
片段着色器是大多数光照计算发生的地方。
材料
// …
uniform struct
{ vec4 ambient
; vec4 diffuse
; vec4 emission
; vec3 specular
; float shininess
;
} p3d_Material;
// …
Panda3D为我们提供了当前渲染的网格或模型的材质(以结构的形式)。
多盏灯
// …
vec4 diffuse = vec4(0.0, 0.0, 0.0, diffuseTex.a);
vec4 specular = vec4(0.0, 0.0, 0.0, diffuseTex.a);
// …
在循环场景的灯光之前,为漫反射颜色和镜面反射颜色创建一个累加器。
// …
for (int i = 0; i < p3d_LightSource.length(); ++i) {
// …
}
// …
现在你可以在灯光间循环,计算每一个灯光的漫射和镜面反射颜色。
光相关向量
在这里,你可以看到四个主要的向量,你需要计算每束光的漫射和镜面反射颜色。光方向向量是指向光的浅蓝色箭头。法向量是直立的绿色箭头。反射向量是反映光方向向量的深蓝色箭头。眼睛或视图向量是指向摄像机的橙色箭头。
// …
vec3 lightDirection =
p3d_LightSource[i].position.xyz
- vertexPosition.xyz
* p3d_LightSource[i].position.w;
// ...
光的方向是从顶点的位置到光的位置。
Panda3D套件p3d _光源[1]。如果这是方向灯,则归零。方向灯没有位置,因为它们只有方向。因此,如果这是一个定向光,光的方向将是潘达3D设定的负方向或相反方向p3d _光源[1]。成为方向方向灯。
// …
normal = normalize(vertexNormal);
// …
你需要顶点法线是一个单位向量。单位向量的长度为1。
// …
vec3 unitLightDirection = normalize(lightDirection);
vec3 eyeDirection = normalize(-vertexPosition.xyz);
vec3 reflectedDirection = normalize(-reflect(unitLightDirection, normal));
// ...
接下来你还需要三个向量。
你需要取包含光方向的点积,这样最好把它标准化。这使它的距离或大小为1(单位矢量)。
眼睛方向与顶点/片段位置相反,因为顶点/片段位置相对于相机位置。请记住,顶点/片段位置在视图空间中。所以不是从相机(眼睛)到顶点/片段,而是从顶点/片段到眼睛(相机)。
这反射向量是光在表面法线方向的反射。当光“射线”击中表面时,它以与入射时相同的角度反弹。光方向向量与法线之间的角度称为“入射角”。反射向量与法线之间的角度称为“反射角”。
你必须否定反射光矢量,因为它需要指向与眼睛矢量相同的方向。记住眼睛的方向是从顶点/片段到相机位置。您将使用反射向量来计算镜面高光的强度。
传播
// …
float diffuseIntensity = max(dot(normal, unitLightDirection), 0.0);
if (diffuseIntensity > 0) {
// ...
}
// ...
漫射强度是表面法线和单位矢量光方向之间的点积。点积的范围可以从负1到1。如果两个矢量指向同一个方向,强度为1。任何其他情况都将少于一个。
当光矢量接近法线方向时,漫射强度接近法线方向。
// ...
if (diffuseIntensity > 0) {
// ...
如果漫射强度为零或更小,请转到下一盏灯。
// ...
vec4 diffuseTemp =
vec4
( clamp
( diffuseTex.rgb
* p3d_LightSource[i].diffuse.rgb
* diffuseIntensity
, 0
, 1
)
, diffuseTex.a
);
diffuseTemp = clamp(diffuseTemp, vec4(0), diffuseTex);
// ...
现在,您可以计算这种光造成的漫射颜色。如果漫射强度为1,漫射颜色将是漫射纹理颜色和灯光颜色的混合。任何其他强度都会导致漫射颜色变暗。
请注意,我是如何将漫射颜色夹在与漫射纹理颜色一样明亮的位置的。这将保护场景不被过度曝光。创建漫反射纹理时,请确保创建它们时它们是完全亮着的。
镜子的
漫射后,出现镜面反射。
// …
float specularIntensity = max(dot(reflectedDirection, eyeDirection), 0);
vec4 specularTemp =
clamp
( vec4(p3d_Material.specular, 1)
* p3d_LightSource[i].specular
* pow
( specularIntensity
, p3d_Material.shininess
)
, 0
, 1
);
// ...
镜面强度是眼睛矢量和反射矢量之间的点积。和漫射强度一样,如果两个矢量指向同一个方向,镜面反射强度为1。任何其他强度都将减少这种光产生的镜面颜色。
材质亮度决定了镜面高光的分布。这通常是在像搅拌机这样的建模程序中设置的。在搅拌机中,它被称为镜面硬度。
聚光灯
// …
float unitLightDirectionDelta =
dot
( normalize(p3d_LightSource[i].spotDirection)
, -unitLightDirection
);
if (unitLightDirectionDelta >= p3d_LightSource[i].spotCosCutoff) {
// ...
}
// ...
}
该片段防止聚光灯圆锥体或平截头体之外的片段受到光线的影响。幸运的是,潘达3D设置 spotDirection和spotCosCutoff截止也适用于方向灯和点光源。聚光灯有位置和方向。但是,方向灯只有一个方向,而点光源只有一个位置。尽管如此,该代码对所有三种灯都有效,避免了对嘈杂if语句的需要。
// …
, -unitLightDirection
// ...
你必须否定unitLightDirection。unitLightDirection从片段到聚光灯,你需要它从聚光灯到片段spotDirection直接沿着聚光灯平截头体的中心向下,离开聚光灯的位置一定距离。
spotCosCutoff = cosine(0.5 * spotlightLensFovAngle);
对于聚光灯,如果片段到光矢量和聚光灯方向矢量之间的点积小于聚光灯视场角一半的余弦,着色器将忽略该光的影响。
对于方向灯和点光源,Panda3D设置spotCosCutoff截止负一个。回想一下,点积的范围从负1到1。所以不管发生什么unitLightDirectionDelta是因为它总是大于或等于负一。
// …
diffuseTemp *= pow(unitLightDirectionDelta, p3d_LightSource[i].spotExponent);
// ...
就像unitLightDirectionDelta片段,这个片段也适用于所有三种灯光类型。对于聚光灯,当您靠近聚光灯平截头体的中心时,这将使片段更亮。对于方向灯和点光源,spot指数为零。回想一下,任何零幂的东西都是1,所以漫射颜色是自身的1倍,意味着它是不变的。
阴影
// …
float shadow =
textureProj
( p3d_LightSource[i].shadowMap
, vertexInShadowSpaces[i]
);
diffuseTemp.rgb *= shadow;
specularTemp.rgb *= shadow;
// ...
Panda3D通过为每个场景光提供阴影贴图和阴影变换矩阵,使得应用阴影相对容易。要自己创建阴影变换矩阵,您需要组装一个矩阵,将视图空间坐标转换为光空间(坐标相对于光的位置)。要自己创建阴影贴图,您需要从灯光的角度将场景渲染为帧缓冲纹理。帧缓冲纹理必须保持光线到碎片的距离。这就是所谓的“深度图”。最后,你需要手动给你的着色器你的DIY深度图均匀样本2DShadow你自己动手做的阴影变换矩阵均匀mat4。此时,您已经自动重现了潘达3D为您所做的一切。
显示的阴影片段使用纹理概述这不同于纹理前面显示的功能。纹理概述第一次分裂[一世】。xyz经过[一世】。w。此后,它使用[一世】。正常男性染色体组型定位阴影贴图中存储的深度。接下来它使用[一世】。z要将此顶点的深度与阴影贴图深度进行比较,请执行以下操作[一世】。正常男性染色体组型。如果比较通过,纹理概述会归还一个。否则,它将返回零。零表示该顶点/片段在阴影中,一表示该顶点/片段不在阴影中。
纹理概述也可以根据阴影贴图的设置返回一个介于0和1之间的值。在这种情况下,纹理概述使用相邻深度值执行多个深度测试,并返回加权平均值。这个加权平均值可以让阴影看起来更柔和。
衰减
// …
float lightDistance = length(lightDirection);
float attenuation =
1
/ ( p3d_LightSource[i].constantAttenuation
+ p3d_LightSource[i].linearAttenuation
* lightDistance
+ p3d_LightSource[i].quadraticAttenuation
* (lightDistance * lightDistance)
);
diffuseTemp.rgb *= attenuation;
specularTemp.rgb *= attenuation;
// ...
光的距离就是光方向向量的大小或长度。请注意,它没有使用标准化的光方向,因为距离是一。
你需要光的距离来计算衰减。衰减意味着光的影响随着你远离它而减弱。
你可以设置常数枚举,线性衰减,和四次采样任何你想要的价值观。一个好的起点是常数枚举= 1,线性衰减= 0,和四次采样= 1。有了这些设置,衰减在光的位置为1,当你移动得更远时,衰减接近零。
最终浅色
// …
diffuse += diffuseTemp;
specular += specularTemp;
// ...
要计算最终的光颜色,请将漫反射和镜面反射相加。在场景灯光中循环时,一定要将它添加到累加器中。
周围的
// …
uniform sampler2D p3d_Texture1;
// …
uniform struct
{ vec4 ambient
;
} p3d_LightModel;
// …
in vec2 diffuseCoord;
// …
vec4 diffuseTex = texture(p3d_Texture1, diffuseCoord);
// …
vec4 ambient = p3d_Material.ambient * p3d_LightModel.ambient * diffuseTex;
// …
照明模型的环境组件基于材质的环境颜色、环境光的颜色和漫射纹理颜色。
应该只有一盏环境光。因此,环境颜色计算只需要进行一次。将此与必须为每个点/方向/点光累积的漫射和镜面颜色进行对比。当你到达SSAO,您将重新访问环境颜色计算。
// …
vec4 outputColor = ambient + diffuse + specular + p3d_Material.emission;
// …
最终颜色是环境颜色、漫射颜色、镜面颜色和发射颜色的总和。