纹理与光照
纹理映射
纹理映射是将图像贴到 3D 模型表面的技术,使模型更加真实。
纹理坐标系
WebGL 使用归一化的纹理坐标系统:
code
(0, 1) ──────── (1, 1)
│ │
│ 纹理图像 │
│ │
(0, 0) ──────── (1, 0)纹理坐标 s 对应水平方向,t 对应垂直方向(在 GLSL 中也称为 x/y 或 u/v)。
创建纹理
javascript
// 创建纹理对象
const texture = gl.createTexture();
// 绑定纹理
gl.bindTexture(gl.TEXTURE_2D, texture);
// 加载图像
const image = new Image();
image.onload = function() {
// 配置纹理
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
// 设置纹理参数
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
};
image.src = 'texture.png';纹理参数
纹理环绕方式
| 参数 | 说明 |
|---|---|
gl.REPEAT | 重复纹理 |
gl.CLAMP_TO_EDGE | 边缘拉伸 |
gl.MIRRORED_REPEAT | 镜像重复 |
javascript
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);纹理过滤方式
| 参数 | 说明 | 性能 |
|---|---|---|
gl.NEAREST | 最近邻过滤 | 快 |
gl.LINEAR | 线性过滤 | 中 |
gl.NEAREST_MIPMAP_NEAREST | 最近邻 Mipmap | 中 |
gl.LINEAR_MIPMAP_NEAREST | 线性 Mipmap | 慢 |
gl.NEAREST_MIPMAP_LINEAR | 最近邻线性 Mipmap | 慢 |
gl.LINEAR_MIPMAP_LINEAR | 三线性过滤 | 最慢 |
javascript
// 缩小过滤
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
// 放大过滤
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
// 生成 Mipmap
gl.generateMipmap(gl.TEXTURE_2D);使用纹理
顶点着色器:
glsl
attribute vec3 a_Position;
attribute vec2 a_TexCoord;
varying vec2 v_TexCoord;
void main() {
gl_Position = vec4(a_Position, 1.0);
v_TexCoord = a_TexCoord;
}片段着色器:
glsl
precision mediump float;
uniform sampler2D u_Texture;
varying vec2 v_TexCoord;
void main() {
gl_FragColor = texture2D(u_Texture, v_TexCoord);
}JavaScript 代码:
javascript
// 顶点数据(位置 + 纹理坐标)
const vertices = new Float32Array([
// x, y, z, s, t
-0.5, 0.5, 0.0, 0.0, 1.0, // 左上
-0.5, -0.5, 0.0, 0.0, 0.0, // 左下
0.5, 0.5, 0.0, 1.0, 1.0, // 右上
0.5, -0.5, 0.0, 1.0, 0.0 // 右下
]);
// 设置纹理单元
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
// 将纹理单元传递给着色器
const u_Texture = gl.getUniformLocation(program, 'u_Texture');
gl.uniform1i(u_Texture, 0); // 纹理单元 0多重纹理
WebGL 支持同时使用多个纹理。
顶点着色器
glsl
attribute vec3 a_Position;
attribute vec2 a_TexCoord;
varying vec2 v_TexCoord;
void main() {
gl_Position = vec4(a_Position, 1.0);
v_TexCoord = a_TexCoord;
}片段着色器
glsl
precision mediump float;
uniform sampler2D u_Texture0;
uniform sampler2D u_Texture1;
varying vec2 v_TexCoord;
void main() {
vec4 color0 = texture2D(u_Texture0, v_TexCoord);
vec4 color1 = texture2D(u_Texture1, v_TexCoord);
// 混合两个纹理
gl_FragColor = mix(color0, color1, 0.5);
}JavaScript 代码
javascript
// 创建并配置纹理 0
const texture0 = gl.createTexture();
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture0);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image0);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
// 创建并配置纹理 1
const texture1 = gl.createTexture();
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, texture1);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image1);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
// 设置纹理单元
gl.useProgram(program);
gl.uniform1i(gl.getUniformLocation(program, 'u_Texture0'), 0);
gl.uniform1i(gl.getUniformLocation(program, 'u_Texture1'), 1);光照基础
光照是 3D 图形中最核心的技术之一,使物体呈现立体感。
光照模型
环境光(Ambient Light)
均匀照亮场景的基础光照:
glsl
uniform vec3 u_AmbientLight; // 环境光颜色
uniform vec3 u_AmbientMaterial; // 材质环境光反射系数
void main() {
vec3 ambient = u_AmbientLight * u_AmbientMaterial;
}漫反射光(Diffuse Light)
Lambert 漫反射模型:
glsl
uniform vec3 u_LightPosition; // 光源位置
uniform vec3 u_DiffuseLight; // 漫反射光颜色
uniform vec3 u_DiffuseMaterial; // 材质漫反射系数
varying vec3 v_Normal; // 法线
varying vec3 v_Position; // 顶点位置
void main() {
// 光线方向
vec3 lightDir = normalize(u_LightPosition - v_Position);
// 法线方向
vec3 normal = normalize(v_Normal);
// 漫反射强度
float diff = max(dot(normal, lightDir), 0.0);
// 漫反射分量
vec3 diffuse = diff * u_DiffuseLight * u_DiffuseMaterial;
}镜面反射光(Specular Light)
Phong 镜面反射模型:
glsl
uniform vec3 u_LightPosition;
uniform vec3 u_CameraPosition;
uniform vec3 u_SpecularLight;
uniform vec3 u_SpecularMaterial;
uniform float u_Shininess; // 高光指数
void main() {
vec3 normal = normalize(v_Normal);
vec3 lightDir = normalize(u_LightPosition - v_Position);
// 反射方向
vec3 reflectDir = reflect(-lightDir, normal);
// 视线方向
vec3 viewDir = normalize(u_CameraPosition - v_Position);
// 镜面反射强度
float spec = pow(max(dot(viewDir, reflectDir), 0.0), u_Shininess);
// 镜面反射分量
vec3 specular = spec * u_SpecularLight * u_SpecularMaterial;
}Blinn-Phong 模型
比 Phong 更高效的镜面反射计算:
glsl
void main() {
vec3 normal = normalize(v_Normal);
vec3 lightDir = normalize(u_LightPosition - v_Position);
vec3 viewDir = normalize(u_CameraPosition - v_Position);
// 半角向量
vec3 halfDir = normalize(lightDir + viewDir);
// 镜面反射强度
float spec = pow(max(dot(normal, halfDir), 0.0), u_Shininess);
vec3 specular = spec * u_SpecularLight * u_SpecularMaterial;
}Phong 光照模型
完整的 Phong 光照模型组合了上述三种光照:
glsl
// 顶点着色器
attribute vec3 a_Position;
attribute vec3 a_Normal;
uniform mat4 u_ModelMatrix;
uniform mat4 u_ViewMatrix;
uniform mat4 u_ProjectionMatrix;
varying vec3 v_Position;
varying vec3 v_Normal;
void main() {
vec4 worldPosition = u_ModelMatrix * vec4(a_Position, 1.0);
v_Position = worldPosition.xyz;
// 法线变换
mat3 normalMatrix = mat3(transpose(inverse(u_ModelMatrix)));
v_Normal = normalMatrix * a_Normal;
gl_Position = u_ProjectionMatrix * u_ViewMatrix * worldPosition;
}glsl
// 片段着色器
precision mediump float;
uniform vec3 u_LightPosition;
uniform vec3 u_CameraPosition;
// 光源属性
uniform vec3 u_AmbientLight;
uniform vec3 u_DiffuseLight;
uniform vec3 u_SpecularLight;
// 材质属性
uniform vec3 u_AmbientMaterial;
uniform vec3 u_DiffuseMaterial;
uniform vec3 u_SpecularMaterial;
uniform float u_Shininess;
varying vec3 v_Position;
varying vec3 v_Normal;
void main() {
vec3 normal = normalize(v_Normal);
vec3 lightDir = normalize(u_LightPosition - v_Position);
vec3 viewDir = normalize(u_CameraPosition - v_Position);
// 环境光
vec3 ambient = u_AmbientLight * u_AmbientMaterial;
// 漫反射
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = diff * u_DiffuseLight * u_DiffuseMaterial;
// 镜面反射
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), u_Shininess);
vec3 specular = spec * u_SpecularLight * u_SpecularMaterial;
// 最终颜色
vec3 color = ambient + diffuse + specular;
gl_FragColor = vec4(color, 1.0);
}光照类型
点光源
从一点向所有方向发射光线:
glsl
uniform vec3 u_LightPosition;
uniform vec3 u_LightColor;
uniform float u_LightIntensity;
// 光照衰减
float distance = length(u_LightPosition - v_Position);
float attenuation = 1.0 / (1.0 + 0.09 * distance + 0.032 * distance * distance);
vec3 light = u_LightColor * u_LightIntensity * attenuation;方向光
平行光,如太阳光:
glsl
uniform vec3 u_LightDirection; // 光线方向
void main() {
vec3 lightDir = normalize(-u_LightDirection);
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = diff * u_DiffuseLight * u_DiffuseMaterial;
}聚光灯
锥形光源:
glsl
uniform vec3 u_LightPosition;
uniform vec3 u_LightDirection;
uniform float u_CutOff; // 内切角
uniform float u_OuterCutOff; // 外切角
void main() {
vec3 lightDir = normalize(u_LightPosition - v_Position);
vec3 spotDir = normalize(-u_LightDirection);
// 计算角度
float theta = dot(lightDir, spotDir);
float epsilon = u_CutOff - u_OuterCutOff;
float intensity = clamp((theta - u_OuterCutOff) / epsilon, 0.0, 1.0);
// 应用光照
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = diff * u_DiffuseLight * u_DiffuseMaterial * intensity;
}材质系统
材质结构体
glsl
struct Material {
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
sampler2D diffuseMap;
sampler2D specularMap;
sampler2D normalMap;
};
uniform Material u_Material;光源结构体
glsl
struct Light {
vec3 position;
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float cutOff;
float outerCutOff;
float constant;
float linear;
float quadratic;
};
uniform Light u_Light;完整光照计算函数
glsl
vec3 calculateLight(Light light, Material material, vec3 normal, vec3 fragPos, vec3 viewDir) {
// 环境光
vec3 ambient = light.ambient * vec3(texture(material.diffuseMap, v_TexCoord));
// 光线方向
vec3 lightDir;
float attenuation = 1.0;
if (light.position.w == 0.0) {
// 方向光
lightDir = normalize(-light.direction);
} else {
// 点光源
lightDir = normalize(light.position.xyz - fragPos);
float distance = length(light.position.xyz - fragPos);
attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * distance * distance);
}
// 漫反射
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuseMap, v_TexCoord));
// 镜面反射
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = light.specular * spec * vec3(texture(material.specularMap, v_TexCoord));
return (ambient + diffuse + specular) * attenuation;
}PBR 材质(基于物理的渲染)
glsl
struct PBRMaterial {
vec3 albedo; // 基础颜色
float metallic; // 金属度
float roughness; // 粗糙度
float ao; // 环境遮蔽
sampler2D albedoMap;
sampler2D normalMap;
sampler2D metallicMap;
sampler2D roughnessMap;
sampler2D aoMap;
};
// PBR 光照计算
vec3 calculatePBRLight(vec3 L, vec3 V, vec3 N, PBRMaterial material) {
vec3 H = normalize(V + L);
// 计算各向同性 BRDF
float NDF = DistributionGGX(N, H, material.roughness);
float G = GeometrySmith(N, V, L, material.roughness);
vec3 F = fresnelSchlick(max(dot(H, V), 0.0), material.albedo, material.metallic);
vec3 numerator = NDF * G * F;
float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001;
vec3 specular = numerator / denominator;
vec3 kS = F;
vec3 kD = vec3(1.0) - kS;
kD *= 1.0 - material.metallic;
float NdotL = max(dot(N, L), 0.0);
return (kD * material.albedo / PI + specular) * NdotL;
}阴影技术
阴影贴图原理
阴影贴图通过从光源视角渲染深度图来实现阴影效果。
基本步骤
- 从光源视角渲染深度图
- 在正常渲染时比较深度值
创建深度贴图帧缓冲
javascript
function createShadowMap(gl, width, height) {
// 创建帧缓冲
const framebuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
// 创建深度纹理
const depthTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, depthTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT24, width, height, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_INT, null);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
// 附加到帧缓冲
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depthTexture, 0);
// 检查状态
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
console.error('阴影贴图帧缓冲不完整');
}
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
return { framebuffer, depthTexture };
}深度渲染着色器
glsl
// 深度顶点着色器
uniform mat4 u_LightSpaceMatrix;
void main() {
gl_Position = u_LightSpaceMatrix * vec4(a_Position, 1.0);
}
// 深度片段着色器(空着色器,仅记录深度)
void main() {
// 深度自动写入
}阴影计算着色器
glsl
uniform sampler2D u_ShadowMap;
uniform mat4 u_LightSpaceMatrix;
float calculateShadow(vec4 fragPosLightSpace, vec3 normal, vec3 lightDir) {
// 执行透视除法
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
projCoords = projCoords * 0.5 + 0.5;
// 获取最近深度
float closestDepth = texture(u_ShadowMap, projCoords.xy).r;
// 当前片段深度
float currentDepth = projCoords.z;
// 偏移(解决阴影失真)
float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005);
// PCF 软阴影
float shadow = 0.0;
vec2 texelSize = 1.0 / vec2(textureSize(u_ShadowMap, 0));
for (int x = -1; x <= 1; ++x) {
for (int y = -1; y <= 1; ++y) {
float pcfDepth = texture(u_ShadowMap, projCoords.xy + vec2(x, y) * texelSize).r;
shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0;
}
}
shadow /= 9.0;
// 超出视锥体范围
if (projCoords.z > 1.0) {
shadow = 0.0;
}
return shadow;
}级联阴影(CSM)
用于大场景的阴影技术:
javascript
// 计算级联分割
function calculateCascadeSplits(near, far, cascadeCount) {
const splits = [];
for (let i = 0; i < cascadeCount; i++) {
const p = (i + 1) / cascadeCount;
const uniform = near + (far - near) * p;
const log = near * Math.pow(far / near, p);
splits.push(0.5 * (uniform + log));
}
return splits;
}法线贴图
法线贴图用于在低模上模拟高模的细节。
切线空间
glsl
attribute vec3 a_Tangent;
attribute vec3 a_Bitangent;
varying mat3 v_TBN;
void main() {
vec3 T = normalize(mat3(u_ModelMatrix) * a_Tangent);
vec3 B = normalize(mat3(u_ModelMatrix) * a_Bitangent);
vec3 N = normalize(mat3(u_ModelMatrix) * a_Normal);
v_TBN = mat3(T, B, N);
}使用法线贴图
glsl
uniform sampler2D u_NormalMap;
void main() {
// 从法线贴图采样
vec3 normal = texture2D(u_NormalMap, v_TexCoord).rgb;
normal = normalize(normal * 2.0 - 1.0); // 转换到 [-1, 1]
// 转换到世界空间
normal = normalize(v_TBN * normal);
// 应用光照
// ...
}实例:纹理光照立方体
完整示例:
html
<!DOCTYPE html>
<html>
<head>
<title>纹理光照立方体</title>
</head>
<body>
<canvas id="glCanvas" width="600" height="400"></canvas>
<script>
const vertexShaderSource = `
attribute vec3 a_Position;
attribute vec3 a_Normal;
attribute vec2 a_TexCoord;
uniform mat4 u_ModelMatrix;
uniform mat4 u_ViewMatrix;
uniform mat4 u_ProjectionMatrix;
varying vec3 v_Position;
varying vec3 v_Normal;
varying vec2 v_TexCoord;
void main() {
vec4 worldPos = u_ModelMatrix * vec4(a_Position, 1.0);
v_Position = worldPos.xyz;
v_Normal = mat3(u_ModelMatrix) * a_Normal;
v_TexCoord = a_TexCoord;
gl_Position = u_ProjectionMatrix * u_ViewMatrix * worldPos;
}
`;
const fragmentShaderSource = `
precision mediump float;
uniform sampler2D u_Texture;
uniform vec3 u_LightPosition;
uniform vec3 u_CameraPosition;
varying vec3 v_Position;
varying vec3 v_Normal;
varying vec2 v_TexCoord;
void main() {
vec3 normal = normalize(v_Normal);
vec3 lightDir = normalize(u_LightPosition - v_Position);
vec3 viewDir = normalize(u_CameraPosition - v_Position);
// 环境光
float ambient = 0.3;
// 漫反射
float diff = max(dot(normal, lightDir), 0.0);
// 镜面反射
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0);
// 纹理颜色
vec4 texColor = texture2D(u_Texture, v_TexCoord);
// 最终颜色
float light = ambient + diff * 0.7 + spec * 0.3;
gl_FragColor = vec4(texColor.rgb * light, texColor.a);
}
`;
// ... 其他初始化代码 ...
</script>
</body>
</html>小结
核心概念
- 纹理映射:将图像贴到模型表面
- 纹理参数:环绕方式和过滤方式
- 光照模型:环境光、漫反射、镜面反射
- 材质系统:物体的光学属性
- 阴影技术:阴影贴图、级联阴影
- 法线贴图:模拟表面细节
性能建议
- 纹理压缩:使用压缩纹理格式
- Mipmap:为纹理生成多级渐远纹理
- 光照优化:减少光源数量,使用简化模型
- 批量渲染:合并相同材质的物体
下一步
继续学习矩阵变换,实现 3D 场景的交互。