Observe · Change · Recompute · Explain
画面使用的代码,就是这里的代码
Read-only · exact runtime source
代码页不提供第二套示例。下面的 GLSL 直接由交互视口导入,并按 decode、TBN、normal matrix、handedness、lighting 和 debug 分段。
这一课怎么学
- 观察
- 预测
- 改一个量
- 看证据
- 再读代码
顶点与空间输入
传递位置、UV、T/B/N 与 handedness。
// SECTION:vertex - carry geometry, tangent and space data
attribute vec4 tangent;
varying vec3 vWorldPosition;
varying vec2 vUv;
varying vec3 vObjectNormal;
varying vec3 vObjectTangent;
varying vec3 vObjectBitangent;
varying vec3 vWorldNormal;
varying vec3 vNaiveWorldNormal;
varying vec3 vWorldTangent;
varying vec3 vWorldBitangent;
varying vec3 vViewNormal;
varying vec3 vViewTangent;
varying vec3 vViewBitangent;
void main() {
vec3 objectNormal = normalize(normal);
vec3 fallbackAxis = abs(objectNormal.y) < 0.95
? vec3(0.0, 1.0, 0.0)
: vec3(1.0, 0.0, 0.0);
vec3 fallbackTangent = normalize(cross(fallbackAxis, objectNormal));
bool hasTangent = length(tangent.xyz) > 0.1;
vec3 objectTangent = hasTangent ? normalize(tangent.xyz) : fallbackTangent;
float tangentHandedness = hasTangent ? (tangent.w < 0.0 ? -1.0 : 1.0) : 1.0;
vec3 objectBitangent = normalize(cross(objectNormal, objectTangent)) * tangentHandedness;
vec4 worldPosition = modelMatrix * vec4(position, 1.0);把 RGB 解码成方向
从 [0, 1] 还原到局部切线空间的 [-1, 1]。
precision highp float;
uniform int uSpace;
uniform int uPattern;
uniform float uNormalStrength;
uniform bool uMirrorUv;
uniform bool uShowRawNormal;
uniform bool uShowPerturbedNormal;
uniform int uDebugMode;
varying vec3 vWorldPosition;
varying vec2 vUv;
varying vec3 vObjectNormal;
varying vec3 vObjectTangent;
varying vec3 vObjectBitangent;
varying vec3 vWorldNormal;
varying vec3 vNaiveWorldNormal;
varying vec3 vWorldTangent;
varying vec3 vWorldBitangent;
varying vec3 vViewNormal;
varying vec3 vViewTangent;
varying vec3 vViewBitangent;
// SECTION:decode - sample a procedural tangent-space normal
vec3 proceduralNormalMap(vec2 uv) {
float x = 0.0;
float y = 0.0;
if (uPattern == 0) {
x = sin(uv.x * 32.0) * cos(uv.y * 22.0);
y = cos(uv.x * 18.0 + uv.y * 27.0);
} else if (uPattern == 1) {
float cell = step(0.5, fract(uv.x * 8.0)) * 2.0 - 1.0;
float row = step(0.5, fract(uv.y * 8.0)) * 2.0 - 1.0;
x = cell * 0.72;
y = row * 0.72;
} else {
vec2 centered = fract(uv) - 0.5;
float radius = length(centered);
x = centered.x / max(radius, 0.08) * sin(radius * 34.0);
y = centered.y / max(radius, 0.08) * sin(radius * 34.0);
}
vec3 normalMap = vec3(
0.5 + x * 0.5 * uNormalStrength,
0.5 + y * 0.5 * uNormalStrength,
0.98
);
return normalize(normalMap * 2.0 - 1.0);
}
float mirrorBitangentSign();TBN 展开
把 tangent-space normal 搬到选定的目标空间。
// SECTION:tbn - expand a local direction through the selected basis
vec3 worldFromTangent(vec3 tangentNormal) {
vec3 bitangent = vWorldBitangent * mirrorBitangentSign();
return normalize(
vWorldTangent * tangentNormal.x +
bitangent * tangentNormal.y +
vWorldNormal * tangentNormal.z
);
}
vec3 objectFromTangent(vec3 tangentNormal) {
vec3 bitangent = vObjectBitangent * mirrorBitangentSign();
return normalize(
vObjectTangent * tangentNormal.x +
bitangent * tangentNormal.y +
vObjectNormal * tangentNormal.z
);
}
vec3 viewFromTangent(vec3 tangentNormal) {
vec3 bitangent = vViewBitangent * mirrorBitangentSign();
return normalize(
vViewTangent * tangentNormal.x +
bitangent * tangentNormal.y +
vViewNormal * tangentNormal.z
);
}
vec3 geometricWorldNormal() {
return normalize(vWorldNormal);
}逆转置法线矩阵
解释非均匀缩放下为什么要保持切平面正交。
// SECTION:normal-matrix - non-uniform scale preserves the normal constraint
mat3 normalMatrix = mat3(transpose(inverse(modelMatrix)));
mat3 modelBasis = mat3(modelMatrix);
vec3 worldNormal = normalize(normalMatrix * objectNormal);
vec3 naiveWorldNormal = normalize(modelBasis * objectNormal);
vec3 worldTangent = normalize(modelBasis * objectTangent);
worldTangent = normalize(worldTangent - worldNormal * dot(worldNormal, worldTangent));
vec3 worldBitangent = normalize(cross(worldNormal, worldTangent)) * tangentHandedness;
vWorldPosition = worldPosition.xyz;
vUv = uv;
vObjectNormal = objectNormal;
vObjectTangent = objectTangent;
vObjectBitangent = objectBitangent;
vWorldNormal = worldNormal;
vNaiveWorldNormal = naiveWorldNormal;
vWorldTangent = worldTangent;
vWorldBitangent = worldBitangent;
vViewNormal = normalize(mat3(viewMatrix) * worldNormal);
vViewTangent = normalize(mat3(viewMatrix) * worldTangent);
vViewBitangent = normalize(mat3(viewMatrix) * worldBitangent);
gl_Position = projectionMatrix * viewMatrix * worldPosition;
}镜像 UV 与副切线
通过 ±1 符号修正镜像 UV 的 B 轴方向。
// SECTION:handedness - mirrored UVs flip the bitangent sign
float mirrorBitangentSign() {
return uMirrorUv ? -1.0 : 1.0;
}
// Select the transformed basis for the requested display space.
vec3 perturbedWorldNormal(vec3 tangentNormal) {
return worldFromTangent(tangentNormal);
}
vec3 selectedSpaceNormal(vec3 tangentNormal, vec3 perturbedNormal) {
if (uSpace == 0) return tangentNormal;
if (uSpace == 1) return objectFromTangent(tangentNormal);
if (uSpace == 3) return viewFromTangent(tangentNormal);
return perturbedNormal;
}
vec3 selectedSpaceTangent() {
if (uSpace == 0) return vec3(1.0, 0.0, 0.0);
if (uSpace == 1) return normalize(vObjectTangent);
if (uSpace == 3) return normalize(vViewTangent);
return normalize(vWorldTangent);
}
vec3 selectedSpaceBitangent() {
if (uSpace == 0) return vec3(0.0, mirrorBitangentSign(), 0.0);
if (uSpace == 1) return normalize(vObjectBitangent * mirrorBitangentSign());
if (uSpace == 3) return normalize(vViewBitangent * mirrorBitangentSign());
return normalize(vWorldBitangent * mirrorBitangentSign());
}
vec3 selectedSpaceGeometricNormal() {
if (uSpace == 0) return vec3(0.0, 0.0, 1.0);
if (uSpace == 1) return normalize(vObjectNormal);
if (uSpace == 3) return normalize(vViewNormal);
return geometricWorldNormal();
}N dot L 与高光
展示扰动法线如何影响光照而不改变几何轮廓。
// SECTION:lighting - the normal changes N dot L, not the silhouette
vec3 finalLighting(vec3 perturbedNormal, vec3 geometricNormal) {
vec3 lightDirection = normalize(vec3(1.8, 2.4, 3.2) - vWorldPosition);
vec3 viewDirection = normalize(cameraPosition - vWorldPosition);
float dotGeometric = max(dot(geometricNormal, lightDirection), 0.0);
float dotPerturbed = max(dot(perturbedNormal, lightDirection), 0.0);
float specular = pow(max(dot(reflect(-lightDirection, perturbedNormal), viewDirection), 0.0), 34.0);
vec3 baseColor = vec3(0.26, 0.52, 0.65);
return baseColor * (0.12 + dotPerturbed * 0.92) + vec3(specular * 0.42) + vec3(dotGeometric * 0.04);
}调试输出
将空间向量和中间量直接编码成可观察证据。
// SECTION:debug - expose the evidence behind the final image
vec3 encodeNormal(vec3 normalValue) {
return normalValue * 0.5 + 0.5;
}
void main() {
vec3 tangentNormal = proceduralNormalMap(vUv);
vec3 geometricNormal = geometricWorldNormal();
vec3 perturbedNormal = perturbedWorldNormal(tangentNormal);
vec3 selectedNormal = selectedSpaceNormal(tangentNormal, perturbedNormal);
vec3 color = finalLighting(perturbedNormal, geometricNormal);
if (uDebugMode == 1) color = encodeNormal(selectedNormal);
if (uDebugMode == 2) color = encodeNormal(selectedSpaceTangent());
if (uDebugMode == 3) color = encodeNormal(selectedSpaceBitangent());
if (uDebugMode == 4) color = encodeNormal(selectedSpaceGeometricNormal());
if (uDebugMode == 5) color = encodeNormal(selectedNormal);
if (uDebugMode == 7) color = encodeNormal(vNaiveWorldNormal);
if (uDebugMode == 6) {
vec3 lightDirection = normalize(vec3(1.8, 2.4, 3.2) - vWorldPosition);
color = vec3(max(dot(perturbedNormal, lightDirection), 0.0));
}
if (uShowRawNormal) color = mix(color, encodeNormal(selectedSpaceGeometricNormal()), 0.55);
if (uShowPerturbedNormal) color = mix(color, encodeNormal(selectedNormal), 0.35);
gl_FragColor = vec4(color, 1.0);
#include <tonemapping_fragment>
#include <colorspace_fragment>
}