Observe · Change · Recompute · Explain
硬件采样器背后的每一个取舍
Read-only · exact runtime source
代码直接来自交互视口,按 UV、LOD、filter、anisotropy、budget、debug 的顺序阅读。
这一课怎么学
- 观察
- 预测
- 改一个量
- 看证据
- 再读代码
UV footprint
UV scale 与导数决定一个 fragment 覆盖多少纹素。
varying vec2 vUv;
varying vec3 vWorldPosition;
void main() {
// SECTION:uv - UV scale and derivatives describe the footprint on the texture
vUv = uv;
vec4 worldPosition = modelMatrix * vec4(position, 1.0);
vWorldPosition = worldPosition.xyz;
gl_Position = projectionMatrix * viewMatrix * worldPosition;
}Mip LOD
rho 与 log2 把 footprint 映射为 mip level。
precision highp float;
uniform sampler2D uTexture;
uniform vec2 uTextureSize;
uniform float uUvScale;
uniform float uLodBias;
uniform float uMipLevels;
uniform float uAnisotropy;
uniform float uSampleBudget;
uniform float uTime;
uniform int uDebugMode;
uniform bool uShowMipGrid;
varying vec2 vUv;
varying vec3 vWorldPosition;
// SECTION:lod - the largest UV derivative estimates the texture footprint
float footprint() {
vec2 dx = dFdx(vUv * uUvScale);
vec2 dy = dFdy(vUv * uUvScale);
return max(length(dx * uTextureSize), length(dy * uTextureSize));
}
float lodLevel(float rho) { return clamp(log2(max(rho, 1.0)) + uLodBias, 0.0, max(uMipLevels - 1.0, 0.0)); }Min / Mag filter
硬件采样器分别处理放大、缩小和 mip 间插值。
// SECTION:filter - the hardware sampler performs the selected nearest/linear and mip interpolation
vec3 sampledColor(vec2 uv) { return texture2D(uTexture, uv * uUvScale).rgb; }Anisotropy
斜视 footprint 需要沿长轴增加读数。
// SECTION:anisotropy - a directional budget is exposed even when the hardware state owns the final taps
float anisotropyCoverage(float rho) {
float elongation = max(rho / max(length(dFdx(vUv * uUvScale)) * uTextureSize.x, 0.0001), 1.0);
return clamp(elongation / max(uAnisotropy, 1.0), 0.0, 1.0);
}Mobile budget
样本、分辨率、压缩和渲染比例共同组成成本。
// SECTION:budget - bytes and taps are shown as a visual budget, not hidden in a magic quality preset
vec3 budgetColor(float rho) {
float taps = max(uSampleBudget, 1.0) * max(uAnisotropy, 1.0);
float pressure = clamp(log2(max(rho, 1.0)) * 0.12 + taps * 0.035, 0.0, 1.0);
return mix(vec3(0.15, 0.8, 0.48), vec3(0.95, 0.25, 0.16), pressure);
}Debug views
把 LOD、模糊、闪烁风险与预算直接输出为颜色。
// SECTION:debug - output the evidence that explains shimmering, blur and cost
void main() {
float rho = footprint();
float lod = lodLevel(rho);
vec2 uv = fract(vUv * uUvScale);
vec3 color = sampledColor(vUv);
if (uDebugMode == 1) color = vec3(uv, 0.0);
if (uDebugMode == 2) color = vec3(fract(lod / max(uMipLevels, 1.0)), step(uMipLevels - 1.0, lod), 0.15);
if (uDebugMode == 3) color = vec3(float(floor(lod)) / max(uMipLevels - 1.0, 1.0));
if (uDebugMode == 4) color = mix(vec3(0.95, 0.25, 0.16), vec3(0.2, 0.82, 0.58), anisotropyCoverage(rho));
if (uDebugMode == 5) color = budgetColor(rho);
if (uDebugMode == 6) color = mix(vec3(0.08, 0.13, 0.2), vec3(0.95, 0.2, 0.14), clamp(log2(max(rho, 1.0)) * 0.18, 0.0, 1.0));
if (uShowMipGrid) color = mix(color, vec3(1.0), step(0.985, fract(vUv.x * uUvScale)) + step(0.985, fract(vUv.y * uUvScale)));
gl_FragColor = vec4(color, 1.0);
#include <tonemapping_fragment>
#include <colorspace_fragment>
}