1.聚光灯的实现
光线从某个点照亮所有方向,而聚光灯只选择其中一个方向,将其他光线方向与所选方向点乘并选择限定范围,判断光线是否在限定范围内,如果不在则不照亮。
首先给定一个点乘限定,如果与选定聚光灯方向的点乘大于这个点乘限定,则照亮,否则不照亮。
片段着色器:
<script id="fragment-shader-3D" type="x-shader/x-fragment">
precision mediump float;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
uniform vec4 u_color;
uniform float u_shininess;
uniform vec3 u_lightDirection;
uniform float u_limit;
void main() {
vec3 normal = normalize(v_normal);
vec3 surfaceToLightDirection = normalize(v_surfaceToLight);
vec3 surfaceToViewDirection = normalize(v_surfaceToView);
vec3 halfVector = normalize(surfaceToLightDirection + surfaceToViewDirection);
float light = 0.0;
float specular = 0.0;
float dotFromDirection = dot(surfaceToLightDirection,-u_lightDirection);
if(dotFromDirection >= u_limit){
light = dot(normal,surfaceToLightDirection);
if(light>0.0){
specular = pow(dot(normal,halfVector),u_shininess);
}
}
gl_FragColor = u_color;
gl_FragColor.rgb *= light;
gl_FragColor.rgb += specular;
}
</script>
对u_lightDirection取负是因为我们希望两个方向匹配的时候能指向相同的方向,也可以传入时就直接传反方向(u_reverseLightDirection)
传参:
var lightdUniformLocation = webgl.getUniformLocation(program, "u_lightDirection");
var limitUniformLocation = webgl.getUniformLocation(program, "u_limit");
//注意这段代码
var lightPosition = [40, 60, 120];
var lmat = m4.lookAt(lightPosition, targetPosition, up);
lmat = m4.multiply(m4.xRotation(lightRotationX), lmat);
lmat = m4.multiply(m4.yRotation(lightRotationY), lmat);
lightDirection = [-lmat[8], -lmat[9], -lmat[10]]
webgl.uniform3fv(lightdUniformLocation, lightDirection);
webgl.uniform1f(limitUniformLocation, Math.cos(limit));
注意到我们的ligtDirection运用到了矩阵的计算,可以联想到之前说过的,如果需要实现视线跟随的效果,那么就需要利用到lookAt()方法。
结果如下:
左右上下移动可改变聚光灯的方向。
片段着色器的优化:
利用step()函数优化if语句,着色器中最好避免出现if语句
float inlight = step(u_limit,dotFromDirection);
float light = inlight * dot(normal,surfaceToLightDirection);
float specular = inlight * pow(dot(normal,halfVector),u_shininess);
2.聚光灯的优化
将聚光灯的光做一个过渡。用一个内部限定和一个外部限定代替原来的一个限定值,如果在内部限定内就使用1,在外部限定外就用0,在二者之间就使用0-1之间的插值。
<script id="fragment-shader-3D" type="x-shader/x-fragment">
precision mediump float;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
uniform vec4 u_color;
uniform float u_shininess;
uniform vec3 u_lightDirection;
uniform float u_innerLimit;
uniform float u_outerLimit;
void main() {
vec3 normal = normalize(v_normal);
vec3 surfaceToLightDirection = normalize(v_surfaceToLight);
vec3 surfaceToViewDirection = normalize(v_surfaceToView);
vec3 halfVector = normalize(surfaceToLightDirection + surfaceToViewDirection);
float dotFromDirection = dot(surfaceToLightDirection,-u_lightDirection);
float limitRange = u_innerLimit - u_outerLimit;
//插值
float inlight = clamp((dotFromDirection - u_outerLimit)/limitRange,0.0,1.0);
float light = inlight * dot(normal,surfaceToLightDirection);
float specular = inlight * pow(dot(normal,halfVector),u_shininess);
gl_FragColor = u_color;
gl_FragColor.rgb *= light;
gl_FragColor.rgb += specular;
}
</script>
注意:必须要保证传到u_innerLimit和u_outerLimit不能相等,否则着色器当中会出现undefined。
定义参数:
var inlimitUniformLocation = webgl.getUniformLocation(program, "u_innerLimit");
var outlimitUniformLocation = webgl.getUniformLocation(program, "u_outerLimit");
webgl.uniform1f(inlimitUniformLocation, Math.cos(innerLimit));
webgl.uniform1f(outlimitUniformLocation, Math.cos(outerLimit));
优化着色器代码(smoothstep代码相当于替代插值函数):
float inlight = smoothstep(u_outerLimit,u_innerLimit,dotFromDirection);
结果如下:
smoothstep使用的时Hermite插值:
当lowBound大于或等于upperBound时,smoothstep方法会产生undefined,对于正常的聚光灯,这种情况永远不会出现。