从零实现Three.js四大核心材质:着色器手写Basic/Lambert/Phong/Standard全解析
本文从底层着色器原理出发,手动实现了Three.js中四种常用材质:基本材质不受光照影响直接显示纹理;兰伯特材质仅计算漫反射,适合粗糙表面;冯氏材质增加高光反射,表现光滑表面;标准材质基于物理渲染,通过金属度和粗糙度模拟真实材质。每种材质均附有顶点和片元着色器代码,并假设场景仅含环境光和平行光及一张漫反射贴图。文章还对比了各材质的特点与适用场景,并提供了BRDF、光照模型等参考资料。
在Three.js开发中,材质是决定物体外观的关键因素。本文将带你从底层原理出发,使用着色器(Shader)手动实现四种常用材质:基本材质、兰伯特材质、冯氏材质和标准材质。为了简化实现,我们假设物体只有一张漫反射贴图,场景中只存在一个环境光和一个平行光。
基本材质(MeshBasicMaterial)
基本材质是最简单的材质,它不对光源产生任何反应,直接显示纹理或颜色。
顶点着色器
varying vec2 vUv;
void main() {
vUv = uv;
vec3 transformed = vec3(position);
vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.0);
gl_Position = projectionMatrix * mvPosition;
}
片元着色器
uniform vec3 diffuse;
uniform float opacity;
uniform sampler2D map;
varying vec2 vUv;
void main() {
vec4 diffuseColor = vec4(diffuse, opacity);
vec4 texelColor = texture2D(map, vUv);
diffuseColor *= texelColor;
gl_FragColor = diffuseColor;
}
兰伯特材质(MeshLambertMaterial)
兰伯特材质只有漫反射,没有高光反射,适合表现粗糙表面(如砖墙、布料)。
顶点着色器
uniform vec3 directColor; // 平行光颜色
uniform vec3 directDirection; // 平行光方向
#define PI 3.14159265359
varying vec2 vUv;
varying vec3 vLightFront;
void main() {
vUv = uv;
vec3 transformedNormal = normalMatrix * vec3(normal);
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * mvPosition;
float dotNL = clamp(dot(normalize(transformedNormal), directDirection), 0.0, 1.0);
vLightFront = dotNL * PI * directColor;
}
片元着色器
uniform vec3 diffuse;
uniform float opacity;
uniform sampler2D map;
uniform vec3 ambientColor;
varying vec2 vUv;
varying vec3 vLightFront;
#define RECIPROCAL_PI 0.31830988618
void main() {
vec4 diffuseColor = vec4(diffuse, opacity);
vec4 texelColor = texture2D(map, vUv);
diffuseColor *= texelColor;
// 出射光 = 直接漫反射 + 间接漫反射
vec3 outgoingLight = vLightFront + ambientColor * RECIPROCAL_PI * diffuseColor.rgb;
gl_FragColor = vec4(outgoingLight, diffuseColor.a);
}
冯氏材质(MeshPhongMaterial)
冯氏材质在漫反射基础上增加了高光反射,需要指定高光颜色(specular)和光亮度(shininess),适合塑料、金属等光滑表面。
顶点着色器
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vViewPosition;
void main() {
vUv = uv;
vNormal = normalize(normalMatrix * normal);
vec3 transformed = vec3(position);
vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.0);
vViewPosition = -mvPosition.xyz;
gl_Position = projectionMatrix * mvPosition;
}
片元着色器
uniform vec3 diffuse;
uniform float opacity;
uniform vec3 specular;
uniform float shininess;
uniform sampler2D map;
uniform vec3 ambientColor;
uniform vec3 directColor;
uniform vec3 directDirection;
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vViewPosition;
#define RECIPROCAL_PI 0.31830988618
// 菲涅尔反射(Schlick近似)
vec3 F_Schlick(const in vec3 specularColor, const in float dotLH) {
float fresnel = exp2((-5.55473 * dotLH - 6.98316) * dotLH);
return (1.0 - specularColor) * fresnel + specularColor;
}
// Blinn-Phong光照模型
float D_BlinnPhong(const in float shininess, const in float dotNH) {
return RECIPROCAL_PI * (shininess * 0.5 + 1.0) * pow(dotNH, shininess);
}
void main() {
vec4 diffuseColor = vec4(diffuse, opacity);
vec4 texelColor = texture2D(map, vUv);
diffuseColor *= texelColor;
// 环境光漫反射
vec3 indirectDiffuse = ambientColor * RECIPROCAL_PI * diffuseColor.rgb;
vec3 normal = normalize(vNormal);
// 平行光漫反射
float dotNL = clamp(dot(normal, directDirection), 0.0, 1.0);
vec3 irradiance = dotNL * directColor;
vec3 directDiffuse = irradiance * RECIPROCAL_PI * diffuseColor.rgb;
// 平行光镜面反射(Blinn-Phong)
vec3 halfDir = normalize(directDirection + normalize(vViewPosition));
float dotNH = clamp(dot(normal, halfDir), 0.0, 1.0);
float dotLH = clamp(dot(directDirection, halfDir), 0.0, 1.0);
vec3 F = F_Schlick(specular, dotLH);
float D = D_BlinnPhong(shininess, dotNH);
vec3 directSpecular = F * (0.25 * D);
// 出射光 = 环境光漫反射 + 平行光漫反射 + 平行光镜面反射
vec3 outgoingLight = indirectDiffuse + directDiffuse + directSpecular;
gl_FragColor = vec4(outgoingLight, diffuseColor.a);
}
标准材质(MeshStandardMaterial)
标准材质是基于物理的渲染(PBR)材质,通过 金属度(metalness) 和 粗糙度(roughness) 两个参数来表现真实材质效果。
顶点着色器
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vViewPosition;
void main() {
vUv = uv;
vNormal = normalize(normalMatrix * vec3(normal));
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
vViewPosition = -mvPosition.xyz;
gl_Position = projectionMatrix * mvPosition;
}
片元着色器
uniform vec3 diffuse;
uniform float opacity;
uniform float metalness;
uniform float roughness;
uniform sampler2D map;
uniform vec3 ambientColor;
uniform vec3 directColor;
uniform vec3 directDirection;
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vViewPosition;
#define RECIPROCAL_PI 0.31830988618
#define EPSILON 1e-6
// 菲涅尔反射
vec3 F_Schlick(const in vec3 specularColor, const in float dotLH) {
float fresnel = exp2((-5.55473 * dotLH - 6.98316) * dotLH);
return (1.0 - specularColor) * fresnel + specularColor;
}
// GGX几何遮蔽函数(Smith相关形式)
float G_GGX_SmithCorrelated(const in float alpha, const in float dotNL, const in float dotNV) {
float a2 = pow2(alpha);
float gv = dotNL * sqrt(a2 + (1.0 - a2) * pow2(dotNV));
float gl = dotNV * sqrt(a2 + (1.0 - a2) * pow2(dotNL));
return 0.5 / max(gv + gl, EPSILON);
}
// GGX微表面分布函数
float D_GGX(const in float alpha, const in float dotNH) {
float a2 = pow2(alpha);
float denom = pow2(dotNH) * (a2 - 1.0) + 1.0;
return RECIPROCAL_PI * a2 / pow2(denom);
}
// GGX镜面反射BRDF
vec3 BRDF_Specular_GGX(const in vec3 directDirection, const in vec3 normal,
const in vec3 viewDir, const in vec3 specularColor,
const in float roughness) {
float alpha = pow2(roughness);
vec3 halfDir = normalize(directDirection + viewDir);
float dotNL = clamp(dot(normal, directDirection), 0.0, 1.0);
float dotNV = clamp(dot(normal, viewDir), 0.0, 1.0);
float dotNH = clamp(dot(normal, halfDir), 0.0, 1.0);
float dotLH = clamp(dot(directDirection, halfDir), 0.0, 1.0);
vec3 F = F_Schlick(specularColor, dotLH);
float G = G_GGX_SmithCorrelated(alpha, dotNL, dotNV);
float D = D_GGX(alpha, dotNH);
return F * (G * D);
}
void main() {
vec4 diffuseColor = vec4(diffuse, opacity);
vec4 texelColor = texture2D(map, vUv);
diffuseColor *= texelColor;
vec3 normal = normalize(vNormal);
// 环境光(间接漫反射)
vec3 indirectDiffuse = ambientColor * RECIPROCAL_PI * diffuseColor.rgb * (1.0 - metalness);
// 平行光
float dotNL = clamp(dot(normal, directDirection), 0.0, 1.0);
vec3 irradiance = dotNL * directColor;
vec3 specularColor = mix(vec3(0.04), diffuseColor.rgb, metalness);
// 直接漫反射
vec3 directDiffuse = irradiance * RECIPROCAL_PI * diffuseColor.rgb * (1.0 - metalness);
// 直接镜面反射
vec3 directSpecular = irradiance * BRDF_Specular_GGX(directDirection, normal,
normalize(vViewPosition), specularColor,
clamp(roughness, 0.04, 1.0));
// 出射光 = 间接漫反射 + 直接漫反射 + 直接镜面反射
vec3 outgoingLight = indirectDiffuse + directDiffuse + directSpecular;
gl_FragColor = vec4(outgoingLight, diffuseColor.a);
}
总结
| 材质 | 特点 | 适用场景 |
|---|---|---|
| BasicMaterial | 无光照,直接显示颜色/纹理 | UI元素、不需要光照的物体 |
| LambertMaterial | 仅漫反射,无高光 | 粗糙表面(砖墙、布料) |
| PhongMaterial | 漫反射 + 高光 | 塑料、金属、光滑表面 |
| StandardMaterial | PBR材质,金属度/粗糙度 | 真实渲染、物理材质 |
参考资料
BRDF-双向反射分布函数:https://baike.baidu.com/item/双向反射分布函数/22311036
常见的三个光照模型:Lambert, Phong, Blinn-Phong:https://blog.csdn.net/taoqilin/article/details/52800702
最后更新于2小时前
本文由人工编写,AI优化,转载请注明原文地址: 从零实现Three.js四大核心材质:着色器手写Basic/Lambert/Phong/Standard全解析
推荐阅读
Windows系统PyTorch安装教程:CUDA 12.1环境配置与TorchText版本兼容性指南
31132024-06-21
达梦数据库libgeos_c.dll加载失败解决方法:空间数据包安装指南
2232026-03-23
CodeBuddyIDE与Trae终极对决:谁是最强国产AI编程IDE?最新版本深度横评
29222025-09-25
XWiki只允许本机访问:Jetty绑定127.0.0.1配置方法
1922026-04-28
GeoServer适配达梦数据库完整教程:从账号创建到图层发布
1992026-04-14
VMware Workstation 16激活码及许可证密钥获取方法
30602024-09-29