三个向量
几乎所有经典光照模型都只围绕三个单位向量展开,它们都在世界空间中计算:
- N:表面法线。插值后长度会变短,片元着色器里要重新
normalize。 - L:从表面指向光源。
- V:从表面指向相机(
cameraPosition - vWorld)。
Lambert 漫反射
粗糙表面把光均匀地散向各个方向,亮度只取决于光线入射角的余弦:
glsl
float NdotL = max(dot(N, L), 0.0);
vec3 color = uAmbient * uAlbedo + uAlbedo * uLightColor * NdotL;max(..., 0.0) 保证背光面不会出现负光。环境光项是对周围间接光的粗略近似。
Phong 高光
光滑表面会把光反射到镜面方向。Phong 先求出反射方向 R,再看它与视线有多接近:
glsl
vec3 R = reflect(-L, N);
float spec = pow(max(dot(R, V), 0.0), uShininess);指数(shininess)越大,高光越小越锐利。
Blinn-Phong 高光
Blinn 改用 L 与 V 的半程向量 H,比较它与法线的夹角。计算更省,掠射角下的高光也更自然。OpenGL 固定管线时代的默认光照就是它。同样的锐度大约需要 Phong 指数的 4 倍:
glsl
vec3 H = normalize(L + V);
float spec = pow(max(dot(N, H), 0.0), uShininess * 4.0);Fresnel 边缘光
现实中,几乎所有材质在掠射角下都会变得更反光(菲涅尔效应)。用 1 - dot(N, V) 来近似:正对相机时为 0,轮廓边缘处趋近 1。乘方控制边缘的宽窄:
glsl
float rim = pow(1.0 - max(dot(N, V), 0.0), uRimPower);它让物体从背景中“跳”出来,常用于角色描边和科幻效果。
Gamma
光照要在线性空间里计算,最后再用 pow(color, 1.0 / 2.2) 编码给显示器。跳过这一步,暗部会显得过黑,过渡也会变得生硬。
核心 API
dotreflectnormalizepowcameraPositionTHREE.ShaderMaterialTHREE.TorusKnotGeometry动手试试
- 1关掉 orbit light,把光固定住,在 Phong 与 Blinn-Phong 之间切换,对比掠射角下的高光形状。
- 2把 shininess 调到 1 和 128,感受“塑料”与“抛光金属”的差别。
- 3加一个 toon 模式:用 floor(NdotL * 3.0) / 3.0 把漫反射量化成色阶。
还没有输出。console.log 的内容会显示在这里。
运行中