光照与材质详解与完整实例
在3D图形渲染中,光照和材质是实现逼真视觉效果的关键因素。通过合理设置光照模型和材质属性,可以使3D场景看起来更加真实。本文将详细介绍Phong光照模型、法向量的作用以及材质属性的设置,并通过一个完整的WebGL实例展示如何实现光照效果。
一、光照模型
(一)Phong光照模型
Phong光照模型是一种常用的光照模型,它将光照效果分为三个主要部分:环境光、漫反射光和镜面反射光。
环境光(Ambient Light):
- 环境光是均匀地照亮整个场景的光,它不依赖于光源的方向或物体的表面特性。
- 公式:
$ - 其中,
是环境光的强度, 是材质的环境光反射率。
漫反射光(Diffuse Light):
- 漫反射光是光源直接照射在物体表面上的光,其强度取决于光源的方向和物体表面的法向量。
- 公式:
- 其中,
是光源的强度, 是材质的漫反射反射率, 是法向量, 是光源方向向量。
镜面反射光(Specular Light):
- 镜面反射光是光源在物体表面反射后形成的高光,其强度取决于光源的方向、观察者的方向和物体表面的光滑度。
- 公式:
- 其中,
是光源的强度, 是材质的镜面反射率, 是反射向量, 是观察者方向向量, 是材质的光滑度(高光指数)。
(二)光照模型的组合
最终的光照效果是环境光、漫反射光和镜面反射光的总和: [ I_{\text{total}} = I_{\text{ambient}} + I_{\text{diffuse}} + I_{\text{specular}} ]
二、法向量
(一)法向量的作用
法向量(Normal Vector)是垂直于物体表面的向量,它在光照计算中起着关键作用。法向量的方向决定了光源对物体表面的影响程度,从而影响漫反射光和镜面反射光的计算。
(二)计算法向量
法向量可以通过多种方式计算,常见的方法包括:
- 几何法:对于简单的几何形状(如立方体、球体等),可以直接计算每个顶点的法向量。
- 微分法:对于复杂的曲面,可以通过微分几何的方法计算法向量。
- 顶点法线平均:对于三角网格,可以通过计算相邻三角形的法向量的平均值来得到顶点的法向量。
(三)传递法向量
在WebGL中,法向量需要作为顶点属性传递给顶点着色器。以下是一个示例代码片段:
javascript
// 定义顶点数据(包括位置和法向量)
const vertices = [
-0.5, -0.5, 0.0, 0.0, 0.0, 1.0, // 左下角
0.5, -0.5, 0.0, 0.0, 0.0, 1.0, // 右下角
0.5, 0.5, 0.0, 0.0, 0.0, 1.0, // 右上角
-0.5, 0.5, 0.0, 0.0, 0.0, 1.0 // 左上角
];
// 创建顶点缓冲区对象
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
// 获取attribute变量的位置
const positionLocation = gl.getAttribLocation(program, 'a_position');
const normalLocation = gl.getAttribLocation(program, 'a_normal');
// 绑定缓冲区到attribute变量
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 24, 0);
gl.enableVertexAttribArray(normalLocation);
gl.vertexAttribPointer(normalLocation, 3, gl.FLOAT, false, 24, 12);
三、材质属性
材质属性决定了物体对光照的响应方式。常见的材质属性包括:
- 环境光反射率(Ambient Reflectivity):
- 漫反射反射率(Diffuse Reflectivity):
- 镜面反射率(Specular Reflectivity):
- 高光指数(Shininess):
(一)设置材质属性
材质属性可以通过uniform变量传递给着色器。以下是一个示例代码片段:
javascript
// 设置材质属性
const material = {
ambient: [0.2, 0.2, 0.2],
diffuse: [0.8, 0.8, 0.8],
specular: [1.0, 1.0, 1.0],
shininess: 32
};
// 获取uniform变量的位置
const ambientLocation = gl.getUniformLocation(program, 'u_material.ambient');
const diffuseLocation = gl.getUniformLocation(program, 'u_material.diffuse');
const specularLocation = gl.getUniformLocation(program, 'u_material.specular');
const shininessLocation = gl.getUniformLocation(program, 'u_material.shininess');
// 传递材质属性到着色器
gl.uniform3fv(ambientLocation, material.ambient);
gl.uniform3fv(diffuseLocation, material.diffuse);
gl.uniform3fv(specularLocation, material.specular);
gl.uniform1f(shininessLocation, material.shininess);
四、完整的WebGL光照实例
以下是一个完整的WebGL实例,展示如何实现Phong光照模型。
(一)HTML文件
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebGL Phong Lighting Example</title>
</head>
<body>
<canvas id="webglCanvas" width="800" height="600"></canvas>
<script src="../webglExamples/base/gl-matrix-min.js"></script>
<script>
const canvas = document.getElementById('webglCanvas');
const gl = canvas.getContext('webgl');
if (!gl) {
alert('Your browser does not support WebGL');
}
// 顶点着色器代码
const vertexShaderSource = `
attribute vec3 a_position;
attribute vec3 a_normal;
varying vec3 v_normal;
varying vec3 v_position;
uniform mat4 u_modelViewProjectionMatrix;
uniform mat4 u_modelViewMatrix;
void main() {
v_normal = mat3(u_modelViewMatrix) * a_normal;
v_position = vec3(u_modelViewMatrix * vec4(a_position, 1.0));
gl_Position = u_modelViewProjectionMatrix * vec4(a_position, 1.0);
}
`;
// 片段着色器代码
const fragmentShaderSource = `
precision mediump float;
varying vec3 v_normal;
varying vec3 v_position;
uniform vec3 u_lightPosition;
uniform vec3 u_lightColor;
uniform vec3 u_materialAmbient;
uniform vec3 u_materialDiffuse;
uniform vec3 u_materialSpecular;
uniform float u_materialShininess;
void main() {
vec3 lightDir = normalize(u_lightPosition - v_position);
vec3 normal = normalize(v_normal);
float diff = max(dot(lightDir, normal), 0.0);
vec3 diffuse = diff * u_lightColor * u_materialDiffuse;
vec3 viewDir = normalize(-v_position);
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), u_materialShininess);
vec3 specular = spec * u_lightColor * u_materialSpecular;
vec3 ambient = u_materialAmbient * u_lightColor;
gl_FragColor = vec4(ambient + diffuse + specular, 1.0);
}
`;
// 创建顶点着色器
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
// 创建片元着色器
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
// 创建程序
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
alert('Unable to initialize the shader program: ' + gl.getProgramInfoLog(program));
}
// 使用程序
gl.useProgram(program);
// 定义顶点数据(包括位置和法向量)
const vertices = [
-0.5, -0.5, 0.0, 0.0, 0.0, 1.0, // 左下角
0.5, -0.5, 0.0, 0.0, 0.0, 1.0, // 右下角
0.5, 0.5, 0.0, 0.0, 0.0, 1.0, // 右上角
-0.5, 0.5, 0.0, 0.0, 0.0, 1.0 // 左上角
];
// 创建顶点缓冲区对象
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
// 获取attribute变量的位置
const positionLocation = gl.getAttribLocation(program, 'a_position');
const normalLocation = gl.getAttribLocation(program, 'a_normal');
// 绑定缓冲区到attribute变量
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 24, 0);
gl.enableVertexAttribArray(normalLocation);
gl.vertexAttribPointer(normalLocation, 3, gl.FLOAT, false, 24, 12);
// 设置视图和投影矩阵
const modelViewProjectionMatrix = glMatrix.mat4.create();
const modelViewMatrix = glMatrix.mat4.create();
const projectionMatrix = glMatrix.mat4.create();
glMatrix.mat4.perspective(projectionMatrix, Math.PI / 4, canvas.width / canvas.height, 0.1, 100.0);
glMatrix.mat4.translate(modelViewMatrix, modelViewMatrix, [0, 0, -5]);
glMatrix.mat4.multiply(modelViewProjectionMatrix, projectionMatrix, modelViewMatrix);
// 获取uniform变量的位置
const modelViewProjectionMatrixLocation = gl.getUniformLocation(program, 'u_modelViewProjectionMatrix');
const modelViewMatrixLocation = gl.getUniformLocation(program, 'u_modelViewMatrix');
const lightPositionLocation = gl.getUniformLocation(program, 'u_lightPosition');
const lightColorLocation = gl.getUniformLocation(program, 'u_lightColor');
const materialAmbientLocation = gl.getUniformLocation(program, 'u_materialAmbient');
const materialDiffuseLocation = gl.getUniformLocation(program, 'u_materialDiffuse');
const materialSpecularLocation = gl.getUniformLocation(program, 'u_materialSpecular');
const materialShininessLocation = gl.getUniformLocation(program, 'u_materialShininess');
// 设置uniform变量的值
gl.uniformMatrix4fv(modelViewProjectionMatrixLocation, false, modelViewProjectionMatrix);
gl.uniformMatrix4fv(modelViewMatrixLocation, false, modelViewMatrix);
gl.uniform3fv(lightPositionLocation, [1.0, 1.0, 1.0]);
gl.uniform3fv(lightColorLocation, [1.0, 1.0, 1.0]);
gl.uniform3fv(materialAmbientLocation, [0.2, 0.2, 0.2]);
gl.uniform3fv(materialDiffuseLocation, [0.8, 0.8, 0.8]);
gl.uniform3fv(materialSpecularLocation, [1.0, 1.0, 1.0]);
gl.uniform1f(materialShininessLocation, 32);
// 清空画布
gl.clearColor(0.0, 0.0, 0.0, 1.0); // 设置背景颜色为黑色
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制矩形
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
</script>
</body>
</html>
(二)代码解析
- 顶点数据:定义了矩形的四个顶点及其法向量。
- 顶点着色器:将顶点位置和法向量传递到片段着色器。
- 片段着色器:根据Phong光照模型计算最终的颜色。
- 视图和投影矩阵:设置视图和投影矩阵,将物体正确投影到屏幕上。
- 材质属性:通过uniform变量传递材质属性到片段着色器。
- 绘制命令:使用
gl.drawArrays
绘制矩形。
通过以上步骤,你可以实现一个简单的Phong光照模型,使物体在光照下呈现出逼真的效果。