1.光照
1)光源类型
- 平行光
- 点光源
- 环境光
2)反射光颜色
- 物体表面的颜色(基底色)和反射特性
- 入射光的颜色与方向
3)反射类型
- 漫反射:针对平行光和点光源而言
- 环境反射:针对环境光而言
4)平行光下漫反射光颜色
漫反射光颜色=入射光颜色x基底色xcos(入射角)
- 入射角:入射光与物体表面法线的夹角
平行光下漫反射光颜色=入射光颜色x基底色xcos(光线方向.法线方向)
attribute vec3 aColor;//基底色
attribute vec3 aNormal;//法向量
attribute vec3 aLightColor;//平行光光线颜色
attribute vec3 aLightDirection;//光线方向
//对法向量进行归一化
vec3 normal = normalize(aNormal.xyz);
//计算光线方向和法向量的点积
float nDotL = max(dot(aLightDirection, normal), 0.0);
//计算漫反射颜色
vec3 diffuse = aLightColor * aColor.rgb * nDotL;
//默认立方体为红色
gl.vertexAttrib3f(gl.getAttribLocation(gl.program, 'aColor'), 1.0, 0.0, 0.0);
//光线颜色
gl.vertexAttrib3f(gl.getAttribLocation(gl.program, 'aLightColor'), 0.8, 0.8, 0.8);
//光照方向
gl.vertexAttrib3f(gl.getAttribLocation(gl.program, 'aLightDirection'),1.0, 0.5, 2.0);
5) 环境反射光颜色
环境反射光颜色=入射光颜色x基底色
attribute vec3 aColor;//基底色
attribute vec3 aAmbientColor;//环境光色
//计算环境光颜色
vec3 ambient=aAmbientColor*aColor.rgb;
//默认立方体为红色
gl.vertexAttrib3f(gl.getAttribLocation(gl.program, 'aColor'), 1.0, 0.0, 0.0);
//环境光线颜色
gl.vertexAttrib3f(gl.getAttribLocation(gl.program, 'aAmbientColor'), 0.3, 0.3, 0.3);
6)物体表面的反射光颜色
物体表面的反射光颜色= 漫反射光颜色+环境反射光颜色
vColor =vec3(diffuse+ambient);
光线颜色=光线每个色值x光线强度,如vec3(1.0, 1.0, 0.0)的黄色光,强度为0.5,则结果为vec3(0.5,0.5,0.0)
7)点光源反射光颜色
点光源反射光颜色,与平行光漫反射类似,只不过需要根据点光源位置计算每个点的入射点光线的方向
attribute vec3 aColor;//基底色
attribute vec3 aNormal;//法向量
attribute vec3 aLightColor;//光线颜色
attribute vec3 aPointLight;//点光源位置
//计算每个点的光线方向
vec3 lightDirection = normalize( aPointLight-gl_Position.xyz);
//对法向量进行归一化
vec3 normal = normalize(aNormal.xyz);
//计算光线方向和法向量的点积
float nDotL = max(dot(lightDirection, normal), 0.0);
//计算漫反射颜色
vec3 diffuse = aLightColor * aColor.rgb * nDotL;
//默认立方体为红色
gl.vertexAttrib3f(gl.getAttribLocation(gl.program, 'aColor'), 1.0, 0.0, 0.0);
//光线颜色
gl.vertexAttrib3f(gl.getAttribLocation(gl.program, 'aLightColor'), 0.8, 0.8, 0.8);
//点光源位置
gl.vertexAttrib3f(gl.getAttribLocation(gl.program, 'aPointLight'), 2.0, 2.0, 2.0);
8)光源不跟着物体动
uniform mat4 uNormalMatrix;//法线矩阵
//对法向量进行归一化
vec3 normal = normalize((uNormalMatrix*vec4(aNormal,1.0)).xyz);
let modelViewMatrix = initModelViewMatrix(gl, settings);
//计算法线矩阵,使得光线不要跟着物体动
let normalMatrix = mat4.create();
mat4.invert(normalMatrix, modelViewMatrix);
mat4.transpose(normalMatrix, normalMatrix);
gl.uniformMatrix4fv(
gl.getUniformLocation(gl.program, 'uNormalMatrix'),
false,
normalMatrix
);
2.贴图
1)简单示例
(1) 顶点着色器
precision mediump float;
attribute vec2 aPosition;
attribute vec2 aTexCoord;
varying vec2 vTexCoord;
void main() {
gl_Position = vec4(aPosition,1.0,1.0);
vTexCoord=aTexCoord;
}
(2)片元着色器
precision mediump float;
varying vec2 vTexCoord;
uniform sampler2D uTex;
void main() {
gl_FragColor = texture2D(uTex, vTexCoord) ;
}
(3)加载贴图
function isPowerOf2(value) {
return (value & (value - 1)) == 0;
}
//加载贴图
function initTexture(gl, url) {
return new Promise((resolve) => {
var image = new Image();
image.src = url; //必须同域
image.onload = () => {
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
if (isPowerOf2(image.width) && isPowerOf2(image.height)) {
gl.generateMipmap(gl.TEXTURE_2D);
} else {
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.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
}
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
resolve({
texture, image });
};
});
}
(4)赋值画图,图片会填充满整个webgl
const positions = [
[-1.0, 1.0],
[1.0, 1.0],
[-1.0, -1.0],
[-1.0, -1.0],
[1.0, 1.0],
[1.0, -1.0]
].reverse();
initArrBuffer(gl, 'aPosition', new Float32Array(flatArr(positions)), 2);
const textureCoord = [
[0.0, 0.0],
[1.0, 0.0],
[0.0, 1.0],
[0.0, 1.0],
[1.0, 0.0],
[1.0, 1.0]
].reverse();
initArrBuffer(gl, 'aTexCoord', new Float32Array(flatArr(textureCoord)), 2);
var {
texture, image } = await initTexture(gl, 'images/a.jpg');
//开启正反面
gl.enable(gl.CULL_FACE);
function drawScene() {
cleanGl(gl);
//画三角形
gl.drawArrays(gl.TRIANGLES, 0, positions.length);
}
drawScene();
gl.enable(gl.CULL_FACE)
:开启正反面,正面三角形的顶点顺序是逆时针方向, 反面三角形是顺时针方向。
2)调换rgb顺序,使得图片变色
gl_FragColor = texture2D(uTex, vTexCoord).bgra;
gl_FragColor = texture2D(uTex, vTexCoord).ggba;
3)维持图片比例大小
- 顶点着色器
precision mediump float;
attribute vec2 aPosition;
attribute vec2 aTexCoord;
attribute vec2 resolution;//webgl宽高
varying vec2 vTexCoord;
void main() {
//图片的坐标转换成[-1.0,1.0]范围
vec2 pos=aPosition/resolution;
gl_Position = vec4(pos.x*2.0-1.0,1.0-pos.y*2.0, 0.0, 1);
vTexCoord=aTexCoord;
}
- 赋值
const textureCoord = [
[0.0, 0.0],
[1.0, 0.0],
[0.0, 1.0],
[0.0, 1.0],
[1.0, 0.0],
[1.0, 1.0]
].reverse();
initArrBuffer(gl, 'aTexCoord', new Float32Array(flatArr(textureCoord)), 2);
var { texture, image } = await initTexture(gl, 'images/a.jpg');
//获取图片各顶点位置
function getImageRect(x, y, width, height) {
var x1 = x;
var x2 = x + width;
var y1 = y;
var y2 = y + height;
return [
[x1, y1],
[x2, y1],
[x1, y2],
[x1, y2],
[x2, y1],
[x2, y2]
].reverse();
}
const positions = getImageRect(0, 0, image.width, image.height);
initArrBuffer(gl, 'aPosition', new Float32Array(flatArr(positions)), 2);
//webgl大小
gl.vertexAttrib2f(
gl.getAttribLocation(program, 'resolution'),
gl.canvas.width,
gl.canvas.height
);
4)给立方体加上贴图
- 顶点着色器
precision mediump float;
attribute vec3 aPosition;
uniform mat4 uProjectionMatrix;
uniform mat4 uModelViewMatrix;
attribute vec2 aTexCoord;
varying vec2 vTexCoord;
void main() {
gl_Position = uProjectionMatrix*uModelViewMatrix * vec4(aPosition,1.0);
vTexCoord=aTexCoord;
}
- 片元着色器
precision mediump float;
uniform sampler2D uTex;
varying vec2 vTexCoord;
void main() {
gl_FragColor = texture2D(uTex, vTexCoord) ;
}
- 参数赋值
//设定texture
var {
texture, image } = await initTexture(gl, 'images/a.jpg');
//texture索引
gl.activeTexture(gl.TEXTURE0);
gl.uniform1i(gl.getUniformLocation(gl.program, 'uTex'), 0);
initArrBuffer(gl, 'aPosition', new Float32Array(flatArr(positions)), 3);
initElmtBuffer(gl, new Uint16Array(flatArr(indices)));
initArrBuffer(gl, 'aTexCoord', new Float32Array(flatArr(textureCoordinates)), 2);
3.动态canvas贴图
1)初始化canvas贴图
function initCanvasTex(gl) {
const canvas = document.createElement('canvas');
canvas.width = 500;
canvas.height = 500;
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'rgb(255,0,0)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
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.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
return {
texture, canvas };
}
const {
texture, canvas } = initCanvasTex(gl);
gl.activeTexture(gl.TEXTURE0);
gl.uniform1i(gl.getUniformLocation(gl.program, 'uTex'), 0);
2)更新canvas并更新到贴图上
let count = 0;
async function drawScene() {
cleanGl(gl);
const ctx = canvas.getContext('2d');
//画canvas
if (count >= 100) {
count = 0;
//清空
ctx.clearRect(0, 0, canvas.width, canvas.height);
//变换底色
// ctx.fillStyle =`rgb(${Math.random() * 255},${Math.random() * 255},${
// Math.random() * 255
// })`;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
ctx.lineWidth = 3;
ctx.strokeStyle = 'white';
//随机画线
ctx.beginPath();
ctx.moveTo(Math.random() * 500, Math.random() * 500);
ctx.lineTo(Math.random() * 500, Math.random() * 500);
ctx.stroke();
ctx.fillStyle = `rgb(${Math.random() * 255},${Math.random() * 255},${
Math.random() * 255
})`;
count++;
//同步更新到贴图上
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
//...
}
可以尝试一下透明底色
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = `rgba(0,0,0,0)`;
4.视频贴图
1)初始化视频贴图
function initVideoTex(gl, url) {
const video = document.createElement('video');
let playing = false;
let timeupdate = false;
video.playsInline = true;
video.muted = true;
video.loop = true;
video.src = url;
video.play();
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
//创建默认蓝色像素到贴图,给video贴图加载时间
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.RGBA,
1,
1,
0,
gl.RGBA,
gl.UNSIGNED_BYTE,
new Uint8Array([0, 0, 255, 255])
);
// 将包裹(wrapping)设置为边缘分割(clamp to edge)
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.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video);
return {
video, texture };
}
var {
video, texture } = await initVideoTex(gl, 'images/test.mp4');
gl.activeTexture(gl.TEXTURE0);
gl.uniform1i(gl.getUniformLocation(gl.program, 'uTex'), 0);
2)更新视频贴图
function drawScene() {
cleanGl(gl);
//更新视频贴图
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video);
//……
}
5.帧缓冲成贴图
帧缓冲用来允许渲染到纹理或渲染到渲染缓冲中的
1)初始化帧缓冲
function initFrameBuffer(gl) {
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
//帧缓冲生成的贴图宽高要与截取webgl的宽高一致
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.RGBA,
gl.canvas.width,
gl.canvas.height,
0,
gl.RGBA,
gl.UNSIGNED_BYTE,
null
);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_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);
var framebuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
framebuffer.texture = texture;
var e = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
if (gl.FRAMEBUFFER_COMPLETE !== e) {
console.log('Frame buffer object is incomplete: ' + e.toString());
return;
}
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.bindTexture(texture, null);
return {
framebuffer, texture };
}
const {
framebuffer, texture: frameTex } = initFrameBuffer(gl);
帧缓冲生成的贴图宽高要与截取webgl的宽高一致,否则有可能物体在画面外,看不见,我这里默认了整个场景
2)画场景
先画需要帧缓冲生成的贴图,然后再画物体,将前面的贴图赋值到物体上
function drawScene() {
if (radius > Math.PI * 2) {
radius = 0;
}
radius += 0.02;
gl.enable(gl.CULL_FACE);
gl.enable(gl.DEPTH_TEST);
//画帧缓冲
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearColor(1, 1, 0, 1); // 黄色底色
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
drawCube();
//画立方体
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
//使用之前生成的贴图
gl.bindTexture(gl.TEXTURE_2D, frameTex);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearColor(0, 0, 0, 1); // 黑色底色
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
drawCube();
requestAnimationFrame(drawScene);
}
3)立方体的贴图
gl.activeTexture(gl.TEXTURE0);
gl.uniform1i(gl.getUniformLocation(gl.program, 'uTex'), 0);
github地址
https://github.com/xiaolidan00/my-webgl
参考
https://webglfundamentals.org/
- 《WebGL编程指南》
- 《webGL 3D开发实战详解 第2版》