文章目录
- 参考
- 效果
- 核心
- 全部代码
- 更新: 多点光(使用结构体)
- TODO: 新增灯光更新着色器 (threejs也是这么做的 所以动态灯光开销颇大(动态visible不需要重新构建着色器))
参考
Blinn–Phong reflection model
效果
平滑着色 | 平直着色 |
---|---|
核心
顶点
// 法线
vNormal = normalMatrix * normal;
vNormal = normalize( vNormal );
// 点光位置
vPointLightPosition = (viewMatrix * vec4( pointLightPosition, 1.0 )).xyz;
//顶点位置
vec3 transformed = vec3( position );
vec4 mvPosition = vec4( transformed, 1.0 );
mvPosition = modelViewMatrix * mvPosition;
vViewPosition = - mvPosition.xyz;
片元
不同着色方式法线设置
#ifdef FLAT_SHADED
vec3 fdx = dFdx( vViewPosition );
vec3 fdy = dFdy( vViewPosition );
vec3 normal = normalize( cross( fdx, fdy ) );
#else
vec3 normal = vNormal ;
normal = normalize( vNormal );
#endif
vec3 geometryPosition = - vViewPosition;
vec3 geometryNormal = normal;
vec3 geometryViewDir = normalize( vViewPosition );
//光线方向
vec3 lVector = vPointLightPosition - geometryPosition;
vec3 lightDirection = normalize( lVector );
//直接反射高光颜色
vec3 phongColor = BRDF_BlinnPhong( lightDirection, geometryViewDir, geometryNormal, vec3(1), 30.);
全部代码
import * as THREE from "three";
const bsdfs_glsl = /* glsl */ `
float pow2( const in float x ) { return x*x; }
vec3 pow2( const in vec3 x ) { return x*x; }
float pow4( const in float x ) { float x2 = x*x; return x2*x2; }
// 距离衰减
float getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {
float distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );
if ( cutoffDistance > 0.0 ) {
distanceFalloff *= pow2( clamp( 1.0 - pow4( lightDistance / cutoffDistance ) ,0. ,1.) );
}
return distanceFalloff;
}
// common.glsl.js
vec3 F_Schlick( const in vec3 f0, const in float f90, const in float dotVH ) {
// Original approximation by Christophe Schlick '94
// float fresnel = pow( 1.0 - dotVH, 5.0 );
// Optimized variant (presented by Epic at SIGGRAPH '13)
// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
float fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );
return f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );
}
float F_Schlick( const in float f0, const in float f90, const in float dotVH ) {
// Original approximation by Christophe Schlick '94
// float fresnel = pow( 1.0 - dotVH, 5.0 );
// Optimized variant (presented by Epic at SIGGRAPH '13)
// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
float fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );
return f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );
}
// bsdfs.glsl.js
float G_BlinnPhong_Implicit( /* const in float dotNL, const in float dotNV */ ) {
// geometry term is (n dot l)(n dot v) / 4(n dot l)(n dot v)
return 0.25;
}
float D_BlinnPhong( const in float shininess, const in float dotNH ) {
return RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );
}
vec3 BRDF_BlinnPhong( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float shininess ) {
vec3 halfDir = normalize( lightDir + viewDir );
float dotNH = clamp( dot( normal, halfDir ) ,0.,1.);
float dotVH = clamp( dot( viewDir, halfDir ) ,0.,1.);
vec3 F = F_Schlick( specularColor, 1.0, dotVH );
float G = G_BlinnPhong_Implicit( /* dotNL, dotNV */ );
float D = D_BlinnPhong( shininess, dotNH );
return F * ( G * D );
}
`;
export class SpecularShaderMaterial extends THREE.ShaderMaterial {
constructor(
params?: ConstructorParameters<typeof THREE.ShaderMaterial>[0] & {
pointLightPosition: THREE.Vector3;
pointLightDistance: number;
pointLightDecay: number;
}
) {
super({
uniforms: {
pointLightPosition: {
value: params!.pointLightPosition,
},
pointLightDistance: {
value: params?.pointLightDistance ?? 100,
},
pointLightDecay: {
value: params?.pointLightDecay ?? 2,
},
},
defines: {
RECIPROCAL_PI: 1 / Math.PI,
// 平直着色 关闭则 平滑着色
FLAT_SHADED: false,
},
vertexShader: /* glsl */ `
varying vec3 vNormal;
varying vec3 vertexPosition;
uniform vec3 pointLightPosition;
varying vec3 vPointLightPosition;
varying vec3 vViewPosition;
void main() {
vNormal = normalMatrix * normal;
vNormal = normalize( vNormal );
vec4 modelViewPosition = modelViewMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * modelViewPosition;
vertexPosition = modelViewPosition.xyz;
vPointLightPosition = (viewMatrix * vec4( pointLightPosition, 1.0 )).xyz;
vec3 transformed = vec3( position );
vec4 mvPosition = vec4( transformed, 1.0 );
mvPosition = modelViewMatrix * mvPosition;
vViewPosition = - mvPosition.xyz;
}`,
fragmentShader: /* glsl */ `
varying vec3 vNormal;
varying vec3 vertexPosition;
varying vec3 vPointLightPosition;
uniform vec3 pointLightPosition;
uniform float pointLightDistance;
uniform float pointLightDecay;
varying vec3 vViewPosition;
${bsdfs_glsl}
void main() {
#ifdef FLAT_SHADED
vec3 fdx = dFdx( vViewPosition );
vec3 fdy = dFdy( vViewPosition );
vec3 normal = normalize( cross( fdx, fdy ) );
#else
vec3 normal = vNormal ;
normal = normalize( vNormal );
#endif
vec3 geometryPosition = - vViewPosition;
vec3 geometryNormal = normal;
vec3 geometryViewDir = normalize( vViewPosition );
// 取自 getPointLightInfo
// vec3 lVector = pointLightPosition - geometryPosition;
vec3 lVector = vPointLightPosition - geometryPosition;
vec3 lightDirection = normalize( lVector );
float lightDistance = length( lVector );
vec3 lightColor = vec3( 1. );
lightColor *= getDistanceAttenuation( lightDistance, pointLightDistance, pointLightDecay );
// 取自 RE_Direct_BlinnPhong
float dotNL = clamp( dot( geometryNormal, lightDirection ) ,0. ,1. );
vec3 irradiance = dotNL * lightColor;
// vec3 reflectedLightDirectSpecular = irradiance * BRDF_BlinnPhong( directLight.direction, geometryViewDir, geometryNormal, material.specularColor, material.specularShininess ) * material.specularStrength;
vec3 phongColor = BRDF_BlinnPhong( lightDirection, geometryViewDir, geometryNormal, vec3(1), 30.);
// gl_FragColor = vec4(vec3( irradiance ) , 1. );
gl_FragColor = vec4(vec3( phongColor ) , 1. );
// gl_FragColor = vec4(vec3( lightDirection ) , 1. );
// gl_FragColor = vec4(vec3( pointLightPosition ) , 1. );
// gl_FragColor = vec4(vec3( vPointLightPosition ) , 1. );
// gl_FragColor = vec4(vec3( normal ) , 1. );
// gl_FragColor = vec4(vec3( lightColor ) , 1. );
// gl_FragColor = vec4(vec3( irradiance ) , 1. );
}`,
});
}
}
更新: 多点光(使用结构体)
import * as THREE from "three";
interface PointLightUniforms {
color: THREE.PointLight["color"];
decay: THREE.PointLight["decay"];
position: THREE.PointLight["position"];
distance: THREE.PointLight["distance"];
}
const bsdfs_glsl = /* glsl */ `
float pow2( const in float x ) { return x*x; }
vec3 pow2( const in vec3 x ) { return x*x; }
float pow4( const in float x ) { float x2 = x*x; return x2*x2; }
// 距离衰减
float getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {
float distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );
if ( cutoffDistance > 0.0 ) {
distanceFalloff *= pow2( clamp( 1.0 - pow4( lightDistance / cutoffDistance ) ,0. ,1.) );
}
return distanceFalloff;
}
// common.glsl.js
vec3 F_Schlick( const in vec3 f0, const in float f90, const in float dotVH ) {
// Original approximation by Christophe Schlick '94
// float fresnel = pow( 1.0 - dotVH, 5.0 );
// Optimized variant (presented by Epic at SIGGRAPH '13)
// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
float fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );
return f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );
}
float F_Schlick( const in float f0, const in float f90, const in float dotVH ) {
// Original approximation by Christophe Schlick '94
// float fresnel = pow( 1.0 - dotVH, 5.0 );
// Optimized variant (presented by Epic at SIGGRAPH '13)
// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
float fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );
return f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );
}
// bsdfs.glsl.js
float G_BlinnPhong_Implicit( /* const in float dotNL, const in float dotNV */ ) {
// geometry term is (n dot l)(n dot v) / 4(n dot l)(n dot v)
return 0.25;
}
float D_BlinnPhong( const in float shininess, const in float dotNH ) {
return RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );
}
vec3 BRDF_BlinnPhong( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float shininess ) {
vec3 halfDir = normalize( lightDir + viewDir );
float dotNH = clamp( dot( normal, halfDir ) ,0.,1.);
float dotVH = clamp( dot( viewDir, halfDir ) ,0.,1.);
vec3 F = F_Schlick( specularColor, 1.0, dotVH );
float G = G_BlinnPhong_Implicit( /* dotNL, dotNV */ );
float D = D_BlinnPhong( shininess, dotNH );
return F * ( G * D );
}
`;
/** Blinn-Phong模型 直接反射高光材质(暂止支持点光源) */
export class SpecularShaderMaterial extends THREE.ShaderMaterial {
PointLights: PointLightUniforms[] = [];
original: ConstructorParameters<typeof THREE.ShaderMaterial>[0];
onBeforeRender(renderer: THREE.WebGLRenderer, scene: THREE.Scene, camera: THREE.Camera): void {
const PointLights: PointLightUniforms[] = [];
const viewMatrix = camera.matrixWorldInverse;
scene.traverse((obj) => {
if (obj.type == "PointLight") {
const light = obj as THREE.PointLight;
const uniforms = {
color: light.color.clone(),
decay: light.decay,
position: new THREE.Vector3(),
distance: light.distance,
/** 采集visible信息 不用更新着色器 (新增灯光则需要重新构建着色器) */
visible: light.visible ? 1 : 0,
};
// uniforms.color.multiplyScalar(light.intensity);
uniforms.position.setFromMatrixPosition(light.matrixWorld);
uniforms.position.applyMatrix4(viewMatrix);
PointLights.push(uniforms);
}
});
// this.fragmentShader = this.replaceLightNums(this.original!.fragmentShader!, {
// numPointLights: PointLights.length,
// });
this.uniforms.pointLights.value = PointLights;
}
constructor(
params?: ConstructorParameters<typeof THREE.ShaderMaterial>[0] & {
// pointLightPosition: THREE.Vector3;
// pointLightDistance: number;
// pointLightDecay: number;
scene: THREE.Scene;
camera: THREE.PerspectiveCamera;
}
) {
const original = {
uniforms: {
pointLights: {
value: [] as PointLightUniforms[],
},
},
defines: {
RECIPROCAL_PI: 1 / Math.PI,
// 平直着色 关闭则 平滑着色
FLAT_SHADED: false,
},
vertexShader: /* glsl */ `
varying vec3 vNormal;
varying vec3 vViewPosition;
void main() {
vNormal = normalMatrix * normal;
vNormal = normalize( vNormal );
vec4 modelViewPosition = modelViewMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * modelViewPosition;
vec3 transformed = vec3( position );
vec4 mvPosition = vec4( transformed, 1.0 );
mvPosition = modelViewMatrix * mvPosition;
vViewPosition = - mvPosition.xyz;
}`,
fragmentShader: /* glsl */ `
varying vec3 vNormal;
varying vec3 vViewPosition;
#if NUM_POINT_LIGHTS > 0
struct PointLight {
vec3 position;
vec3 color;
float distance;
float decay;
bool visible;
};
uniform PointLight pointLights[ NUM_POINT_LIGHTS ];
#endif
${bsdfs_glsl}
void main() {
gl_FragColor = vec4(vec3( 0. ) , 1. );
#if NUM_POINT_LIGHTS > 0
#ifdef FLAT_SHADED
vec3 fdx = dFdx( vViewPosition );
vec3 fdy = dFdy( vViewPosition );
vec3 normal = normalize( cross( fdx, fdy ) );
#else
vec3 normal = vNormal ;
normal = normalize( vNormal );
#endif
vec3 geometryPosition = - vViewPosition;
vec3 geometryNormal = normal;
vec3 geometryViewDir = normalize( vViewPosition );
PointLight pointLight;
vec3 directSpecular = vec3(0.);
for ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {
pointLight = pointLights[ i ];
// 取自 getPointLightInfo
vec3 lVector = pointLight.position - geometryPosition;
vec3 lightDirection = normalize( lVector );
directSpecular += pointLight.visible ? BRDF_BlinnPhong( lightDirection, geometryViewDir, geometryNormal, vec3(1), 30.) : vec3(0.);
}
gl_FragColor = vec4(vec3( directSpecular ) , 1. );
#endif
}
`,
};
const PointLights: PointLightUniforms[] = [];
const viewMatrix = params!.camera.matrixWorldInverse;
params!.scene.traverse((obj) => {
if (obj.type == "PointLight") {
const light = obj as THREE.PointLight;
const uniforms = {
color: light.color.clone(),
decay: light.decay,
position: new THREE.Vector3(),
distance: light.distance,
/** 采集visible信息 不用更新着色器 (新增灯光则需要重新构建着色器) */
visible: light.visible,
};
// uniforms.color.multiplyScalar(light.intensity);
uniforms.position.setFromMatrixPosition(light.matrixWorld);
uniforms.position.applyMatrix4(viewMatrix);
PointLights.push(uniforms);
}
});
const replaceLightNums = (string: string, parameters: { numPointLights: number }) => {
return string.replace(/NUM_POINT_LIGHTS/g, "" + parameters.numPointLights);
};
original.uniforms.pointLights = { value: PointLights };
original.fragmentShader = replaceLightNums(original!.fragmentShader!, {
numPointLights: PointLights.length,
});
super(original);
this.original = original;
}
replaceLightNums(string: string, parameters: { numPointLights: number }) {
return string.replace(/NUM_POINT_LIGHTS/g, "" + parameters.numPointLights);
}
}