const canvas = document.getElementById('glCanvas');
const gl = canvas.getContext('webgl');
const vsSource = `
attribute vec4 aPosition;
attribute vec2 aTexCoord;
attribute vec3 aTangent;
attribute vec3 aNormal;
uniform mat4 uModelView;
varying highp vec2 vTexCoord;
varying highp vec3 vTangent;
varying highp vec3 vBitangent;
varying highp vec3 vNormal;
varying highp vec3 vPosition;
void main() {
gl_Position = aPosition;
vTexCoord = aTexCoord;
vNormal = aNormal;
vTangent = aTangent;
vBitangent = cross(aNormal, aTangent);
vPosition = aPosition.xyz;
}
`;
const fsSource = `
varying highp vec2 vTexCoord;
varying highp vec3 vTangent;
varying highp vec3 vBitangent;
varying highp vec3 vNormal;
varying highp vec3 vPosition;
uniform sampler2D uNormalMap;
void main() {
highp vec3 normalMap = texture2D(uNormalMap, vTexCoord).rgb * 2.0 - 1.0;
highp mat3 TBN = mat3(vTangent, vBitangent, vNormal);
highp vec3 normal = normalize(TBN * normalMap);
highp vec3 lightDir = normalize(vec3(1.0, 1.0, 2.0));
highp vec3 viewDir = normalize(-vPosition);
highp float diff = max(dot(normal, lightDir), 0.0);
highp vec3 diffuse = diff * vec3(0.8, 0.5, 0.3);
highp vec3 reflectDir = reflect(-lightDir, normal);
highp float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0);
highp vec3 specular = spec * vec3(1.0);
highp vec3 ambient = vec3(0.2);
highp vec3 result = ambient + diffuse + specular;
gl_FragColor = vec4(result, 1.0);
}
`;
function initShaderProgram(gl, vsSource, fsSource) {
const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
return shaderProgram;
}
function loadShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
return shader;
}
// Create procedural normal map
function createNormalMap(gl, size) {
const data = new Uint8Array(size * size * 4);
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
const idx = (y * size + x) * 4;
const nx = Math.sin(x * 0.5) * 0.5;
const ny = Math.sin(y * 0.5) * 0.5;
const nz = Math.sqrt(1 - nx*nx - ny*ny);
data[idx] = (nx * 0.5 + 0.5) * 255;
data[idx + 1] = (ny * 0.5 + 0.5) * 255;
data[idx + 2] = (nz * 0.5 + 0.5) * 255;
data[idx + 3] = 255;
}
}
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, size, size, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
return texture;
}
const positions = [-0.7, 0.7, 0, -0.7, -0.7, 0, 0.7, -0.7, 0, 0.7, 0.7, 0];
const texCoords = [0, 0, 0, 1, 1, 1, 1, 0];
const tangents = [1,0,0, 1,0,0, 1,0,0, 1,0,0];
const normals = [0,0,1, 0,0,1, 0,0,1, 0,0,1];
const indices = [0, 1, 2, 0, 2, 3];
const posBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
const texBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texCoords), gl.STATIC_DRAW);
const tanBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, tanBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(tangents), gl.STATIC_DRAW);
const normBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, normBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW);
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
const shaderProgram = initShaderProgram(gl, vsSource, fsSource);
const posAttrib = gl.getAttribLocation(shaderProgram, 'aPosition');
const texAttrib = gl.getAttribLocation(shaderProgram, 'aTexCoord');
const tanAttrib = gl.getAttribLocation(shaderProgram, 'aTangent');
const normAttrib = gl.getAttribLocation(shaderProgram, 'aNormal');
const normalMap = createNormalMap(gl, 64);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);
gl.vertexAttribPointer(posAttrib, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(posAttrib);
gl.bindBuffer(gl.ARRAY_BUFFER, texBuffer);
gl.vertexAttribPointer(texAttrib, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(texAttrib);
gl.bindBuffer(gl.ARRAY_BUFFER, tanBuffer);
gl.vertexAttribPointer(tanAttrib, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(tanAttrib);
gl.bindBuffer(gl.ARRAY_BUFFER, normBuffer);
gl.vertexAttribPointer(normAttrib, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(normAttrib);
gl.useProgram(shaderProgram);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, normalMap);
gl.uniform1i(gl.getUniformLocation(shaderProgram, 'uNormalMap'), 0);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);