Related
if (statuseffect) {
// Clearing the stencil buffer
gl.clearStencil(0);
gl.clear(gl.STENCIL_BUFFER_BIT);
gl.stencilFunc(gl.ALWAYS, 1, 1);
gl.stencilOp(gl.REPLACE, gl.REPLACE, gl.REPLACE);
gl.colorMask(false, false, false, false);
gl.enable(gl.STENCIL_TEST);
// Renders the mask through gl.drawArrays L111
drawImage(statuseffectmask.texture, lerp(-725, 675, this.Transtion_Value), 280, 128 * 4, 32 * 4)
// Telling the stencil now to draw/keep only pixels that equals 1 - which we set earlier
gl.stencilFunc(gl.EQUAL, 1, 1);
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
// enabling back the color buffer
gl.colorMask(true, true, true, true);
drawImage(statuseffect.texture, lerp(-725, 675, this.Transtion_Value), 280, 128 * 4, 32 * 4)
gl.disable(gl.STENCIL_TEST);
}
Im trying to get something to work like this
Where it gets the transparency of the sprite, and then draws a sprite in areas where there is no transparency, thank you.
It's not clear why you want to use the stencil for this. Normally you'd just setup blending and use the transparency to blend.
If you really wanted to use the stencil you'd need to make a shader that calls discard if the transparency (alpha) is less then some value in order to make the stencil get set only where the sprite is not transparent
precision highp float;
varying vec2 v_texcoord;
uniform sampler2D u_texture;
uniform float u_alphaTest;
void main() {
vec4 color = texture2D(u_texture, v_texcoord);
if (color.a < u_alphaTest) {
discard; // don't draw this pixel
}
gl_FragColor = color;
}
But the thing is that would already draw the texture transparently without using the stencil.
const m4 = twgl.m4;
const gl = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 position;
attribute vec2 texcoord;
uniform mat4 u_matrix;
varying vec2 v_texcoord;
void main() {
gl_Position = u_matrix * position;
v_texcoord = texcoord;
}
`;
const fs = `
precision highp float;
varying vec2 v_texcoord;
uniform sampler2D u_texture;
uniform float u_alphaTest;
void main() {
vec4 color = texture2D(u_texture, v_texcoord);
if (color.a < u_alphaTest) {
discard; // don't draw this pixel
}
gl_FragColor = color;
}
`;
// compile shaders, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// make buffers for positions and texcoords for a plane
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData
const bufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);
const texture = makeSpriteTexture(gl, '🌲', 128);
function render(time) {
time *= 0.001; // convert to seconds
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
const mat = m4.ortho(-150, 150, 75, -75, -1, 1);
m4.translate(mat, [Math.cos(time) * 20, Math.sin(time) * 20, 0], mat);
m4.scale(mat, [64, 64, 1], mat);
// calls gl.activeTexture, gl.bindTexture, gl.uniformXXX
twgl.setUniformsAndBindTextures(programInfo, {
u_texture: texture,
u_alphaTest: 0.5,
u_matrix: mat,
});
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, bufferInfo);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
// just so we don't have to download an image
// we'll make our own using a canvas and the 2D API
function makeSpriteTexture(gl, str, size) {
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d');
ctx.font = `${size * 3 / 4}px sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(str, size / 2, size / 2);
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(
gl.TEXTURE_2D,
0, // mip level
gl.RGBA, // internal format
gl.RGBA, // format
gl.UNSIGNED_BYTE, // type,
canvas);
// let's assume we used power of 2 dimensions
gl.generateMipmap(gl.TEXTURE_2D);
return tex;
}
canvas {
background: url(https://i.imgur.com/v38pV.jpg) no-repeat center center;
background-size: cover;
}
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
Otherwise if you really want to use the stencil now that the code is discarding some pixels it should work and your code was correct. note the code below doesn't clear the stencil because it defaults to being cleared every frame
const m4 = twgl.m4;
const gl = document.querySelector('canvas').getContext('webgl', {stencil: true});
const vs = `
attribute vec4 position;
attribute vec2 texcoord;
uniform mat4 u_matrix;
varying vec2 v_texcoord;
void main() {
gl_Position = u_matrix * position;
v_texcoord = texcoord;
}
`;
const fs = `
precision highp float;
varying vec2 v_texcoord;
uniform sampler2D u_texture;
uniform float u_alphaTest;
void main() {
vec4 color = texture2D(u_texture, v_texcoord);
if (color.a < u_alphaTest) {
discard; // don't draw this pixel
}
gl_FragColor = color;
}
`;
// compile shaders, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// make buffers for positions and texcoords for a plane
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData
const bufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);
const starTexture = makeSpriteTexture(gl, '✱', 128);
const bugTexture = makeSpriteTexture(gl, '🐞', 128);
function render(time) {
time *= 0.001; // convert to seconds
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
const mat = m4.ortho(-150, 150, 75, -75, -1, 1);
m4.translate(mat, [Math.cos(time) * 20, 0, 0], mat);
m4.scale(mat, [64, 64, 1], mat);
gl.enable(gl.STENCIL_TEST);
// set the stencil to 1 everwhere we draw a pixel
gl.stencilFunc(gl.ALWAYS, 1, 0xFF);
gl.stencilOp(gl.REPLACE, gl.REPLACE, gl.REPLACE);
// don't render pixels
gl.colorMask(false, false, false, false);
// calls gl.activeTexture, gl.bindTexture, gl.uniformXXX
twgl.setUniformsAndBindTextures(programInfo, {
u_texture: starTexture,
u_alphaTest: 0.5,
u_matrix: mat,
});
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, bufferInfo);
// only draw if the stencil = 1
gl.stencilFunc(gl.EQUAL, 1, 0xFF);
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
// render pixels
gl.colorMask(true, true, true, true);
m4.ortho(-150, 150, 75, -75, -1, 1, mat);
m4.translate(mat, [0, Math.cos(time * 1.1) * 20, 0], mat);
m4.scale(mat, [64, 64, 1], mat);
// calls gl.activeTexture, gl.bindTexture, gl.uniformXXX
twgl.setUniformsAndBindTextures(programInfo, {
u_texture: bugTexture,
u_alphaTest: 0, // draw all pixels (but stencil will prevent some)
u_matrix: mat,
});
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, bufferInfo);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
// just so we don't have to download an image
// we'll make our own using a canvas and the 2D API
function makeSpriteTexture(gl, str, size) {
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d');
ctx.font = `${size * 3 / 4}px sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(str, size / 2, size / 2);
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(
gl.TEXTURE_2D,
0, // mip level
gl.RGBA, // internal format
gl.RGBA, // format
gl.UNSIGNED_BYTE, // type,
canvas);
// let's assume we used power of 2 dimensions
gl.generateMipmap(gl.TEXTURE_2D);
return tex;
}
canvas {
background: url(https://i.imgur.com/v38pV.jpg) no-repeat center center;
background-size: cover;
}
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
Let me also point out that that this is also probably better done using alpha blending, passing both textures into a single shader and passing in another matrix or other uniforms to apply one texture's alpha ot the other. This would be more flexible as you could can blend across all values of 0 to 1 where as with the stencil you can only mask 0 or 1 period.
My point isn't to say "don't use the stencil" but rather that there are times where it's best and times where it's not. Only you can know for your situation which solution to choose.
I'm trying to implement the Phong shading model, but I come across something quite strange. When I change the viewing position, it looks like the light behaves differently, as if it was dependent from the view. Like, if I'm close to the object I only see the effects of the ambient light, while if I go far away from it I start seeing the diffuse's contribution.
These are my shaders:
//Vertex Shader
attribute vec4 vPosition;
attribute vec4 vNormal;
varying vec3 N, L, E;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform vec4 lightPosition;
void main()
{
vec3 pos = -(modelViewMatrix * vPosition).xyz;
vec3 light = lightPosition.xyz;
L = normalize(light - pos);
E = -pos;
N = normalize((modelViewMatrix * vNormal).xyz);
gl_Position = projectionMatrix * modelViewMatrix * vPosition;
}
//Fragment Shader
uniform vec4 ambientProduct;
uniform vec4 diffuseProduct;
uniform vec4 specularProduct;
uniform float shininess;
varying vec3 N, L, E;
void main()
{
vec4 fColor;
vec3 H = normalize(L + E);
vec4 ambient = ambientProduct;
float Kd = max(dot(L, N), 0.0);
vec4 diffuse = Kd * diffuseProduct;
float Ks = pow(max(dot(N, H), 0.0), shininess);
vec4 specular = Ks * specularProduct;
if (dot(L, N) < 0.0) {
specular = vec4(0.0, 0.0, 0.0, 1.0);
}
fColor = ambient + diffuse + specular;
fColor.a = 1.0;
gl_FragColor = fColor;
}
What am I doing wrong? How can I make the light behave independently from the viewer position?
Update 1:
After #Rabbid76's answer I edited the vertex shader by adding these lines (as well as passing the separate model and view matrices but I'll omit that for brevity's sake):
vec3 pos = (modelViewMatrix * vPosition).xyz;
vec3 light = (viewMatrix * lightPosition).xyz;
And I also updated the calculation of the N vector as the previous way of doing it seemed to not actually allow a per-fragment shading:
N = normalize(mat3(modelViewMatrix) * vNormal.xyz);
Still, the shade seems to move along with the rotation of the camera. This could be related to the fact that the light is multiplied by the viewMatrix I guess?
The calculation of the light vector is wrong.
L = normalize(light - pos);
While pos is a position in view space, light is a position in world space. light is the position of the light in the world. So light - pos doesn't make any sense at all. Both vectors have to be related to the same reference systems.
Transform the position of the light source by the view matrix, before you set it to the uniform lightPosition, to solve the issue.
Of course the transformation can also be done in shader code:
uniform mat4 viewMatrix;
uniform mat4 modelViewMatrix;
uniform vec4 lightPosition;
void main()
{
vec3 pos = (modelViewMatrix * vPosition).xyz;
vec3 light = (viewMatrix * lightPosition).xyz;
L = normalize(light - pos);
// ...
}
Further note that the position in view space has not to be inverted. It has to be
vec3 pos = (modelViewMatrix * vPosition).xyz;
rather than
vec3 pos = -(modelViewMatrix * vPosition).xyz;
A working snippet in your question is always helpful!
Issues
The light and the position need to be in the same space.
Those could be world space or view space but they need to be the same space.
The code had the position E in view space but the lightPosition in world space
You can't multiply a normal by a modelViewMatrix
You need to remove the translation. You also potentially need to deal
with scaling issues. See this article
The code is computing values in the vertex shader so they will be interpolated as they get passed to the fragment shader. That means they will no longer be unit vectors so you need to re-normalize them.
In computing the half vector you need to add their directions
The code was adding L (the direction from the surface to the light) to the view position of the surface instead of the direction from the surface to the view.
In computing a surface to light direction that would be light - pos but the code was negating pos. Of course you also need pos to be negative for the surface to view direction E
const gl = document.querySelector('canvas').getContext('webgl');
const m4 = twgl.m4;
const vs = `
attribute vec4 vPosition;
attribute vec4 vNormal;
varying vec3 N, L, E;
uniform mat4 viewMatrix;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform vec4 lightPosition;
void main()
{
vec3 pos = (modelViewMatrix * vPosition).xyz;
vec3 light = (viewMatrix * lightPosition).xyz;
L = light - pos;
E = -pos;
N = mat3(modelViewMatrix) * vNormal.xyz;
gl_Position = projectionMatrix * modelViewMatrix * vPosition;
}
`;
const fs = `
precision highp float;
uniform vec4 ambientProduct;
uniform vec4 diffuseProduct;
uniform vec4 specularProduct;
uniform float shininess;
varying vec3 N, L, E;
void main()
{
vec4 fColor;
vec3 normal = normalize(N);
vec3 surfaceToLightDir = normalize(L);
vec3 surfaceToViewDir = normalize(E);
vec3 H = normalize(surfaceToLightDir + surfaceToViewDir);
vec4 ambient = ambientProduct;
float Kd = max(dot(surfaceToLightDir, normal), 0.0);
vec4 diffuse = Kd * diffuseProduct;
float Ks = pow(max(dot(normal, H), 0.0), shininess);
vec4 specular = Ks * specularProduct;
if (dot(surfaceToLightDir, normal) < 0.0) {
specular = vec4(0.0, 0.0, 0.0, 1.0);
}
fColor = ambient + diffuse + specular;
fColor.a = 1.0;
gl_FragColor = fColor;
}
`;
// compiles shaders, links program, looks up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const vertices = twgl.primitives.createSphereVertices(
2, // radius
8, // subdivision around
6, // subdivisions down
);
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
vPosition: vertices.position,
vNormal: vertices.normal,
indices: vertices.indices,
});
function render(time) {
time *= 0.001; // convert to seconds
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
const projectionMatrix = m4.perspective(
60 * Math.PI / 180, // field of view
gl.canvas.clientWidth / gl.canvas.clientHeight, // aspect
0.1, // znear
100, // zfar
);
const eye = [
Math.sin(time) * 5,
3,
3 + Math.cos(time) * 5,
];
const target = [0, 2, 3];
const up = [0, 1, 0];
const cameraMatrix = m4.lookAt(eye, target, up);
const viewMatrix = m4.inverse(cameraMatrix);
const worldMatrix = m4.translation([0, 2, 3]);
const modelViewMatrix = m4.multiply(viewMatrix, worldMatrix);
const uniforms = {
viewMatrix,
modelViewMatrix,
projectionMatrix,
lightPosition: [4, 3, 1, 1],
ambientProduct: [0, 0, 0, 1],
diffuseProduct: [1, 1, 1, 1],
specularProduct: [1, 1, 1, 1],
shininess: 50,
};
// calls gl.uniformXXX
twgl.setUniforms(programInfo, uniforms);
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, bufferInfo);
// -- not important to answer --
drawLightAndGrid(uniforms)
requestAnimationFrame(render);
}
requestAnimationFrame(render);
// -- ignore below this line. The only point is to give a frame
// of reference.
let gridBufferInfo;
function drawLightAndGrid(sphereUniforms) {
if (!gridBufferInfo) {
const vPosition = [];
const s = 100;
for (let x = -s; x <= s; x += 2) {
vPosition.push(x, 0, -s);
vPosition.push(x, 0, s);
vPosition.push(-s, 0, x);
vPosition.push( s, 0, x);
}
gridBufferInfo = twgl.createBufferInfoFromArrays(gl, {
vPosition,
vNormal: { value: [0, 0, 0], }
});
}
const worldMatrix = m4.translation(sphereUniforms.lightPosition);
m4.scale(worldMatrix, [0.1, 0.1, 0.1], worldMatrix);
const uniforms = Object.assign({}, sphereUniforms, {
modelViewMatrix: m4.multiply(sphereUniforms.viewMatrix, worldMatrix),
ambientProduct: [1, 0, 0, 1],
diffuseProduct: [0, 0, 0, 0],
specularProduct: [0, 0, 0, 0],
});
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, uniforms);
twgl.drawBufferInfo(gl, bufferInfo);
twgl.setBuffersAndAttributes(gl, programInfo, gridBufferInfo);
twgl.setUniforms(programInfo, {
modelViewMatrix: sphereUniforms.viewMatrix,
ambientProduct: [0, 0, 1, 1],
});
twgl.drawBufferInfo(gl, gridBufferInfo, gl.LINES);
}
canvas { border: 1px solid black }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
For me personally I find short cryptic variable names hard to follow but that's a personal preference.
Very new to WebGL and attempting to port some 2D image processing shaders in order to get a handle on things. I initially was misled by the MDN tutorials into thinking WebGL was like OpenGL desktop, but then found these tutorials, which I found much more true to form as well as my purposes. However, I'm still having some trouble in formatting a render loop so I can pass a continually updating texture for processing. In cases where it does render, I just get a muddy mess and in cases where the vertex shader isn't a simple pass through I get nothing. I understand GLSL and the basics of how buffers work, but clearly am doing something very wrong here... Any help would be greatly appreciated. Thanks!
class GL {
constructor(canvas){
this.gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
if (!this.gl) {
alert("Unable to initialize WebGL. Your browser may not support it.");
this.gl = null;
}
//init shaders
var fragmentShader = getShader(this.gl, "fshader");
var vertexShader = getShader(this.gl, "vshader");
var shaderProgram = this.gl.createProgram();
this.gl.attachShader(shaderProgram, vertexShader);
this.gl.attachShader(shaderProgram, fragmentShader);
this.gl.linkProgram(shaderProgram);
this.gl.useProgram(shaderProgram);
this.positionLocation = this.gl.getAttribLocation(shaderProgram, "position");
this.texCoordLocation = this.gl.getAttribLocation(shaderProgram, "texcoord");
var resolutionLocation = this.gl.getUniformLocation(shaderProgram, "resolution");
this.width = this.gl.getUniformLocation(shaderProgram, "width");
//set resolution
this.gl.uniform2f(resolutionLocation, canvas.width, canvas.height);
function getShader(gl, id) {
var shaderScript, theSource, currentChild, shader;
shaderScript = document.getElementById(id);
if (!shaderScript) {
return null;
}
theSource = "";
currentChild = shaderScript.firstChild;
while(currentChild) {
if (currentChild.nodeType == currentChild.TEXT_NODE) {
theSource += currentChild.textContent;
}
currentChild = currentChild.nextSibling;
}
if (shaderScript.type == "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (shaderScript.type == "x-shader/x-vertex") {
shader = gl.createShader(gl.VERTEX_SHADER);
} else {
// Unknown shader type
return null;
}
gl.shaderSource(shader, theSource);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert("An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader));
return null;
}
return shader;
};
};
render(bufferCanvas, x, y) {
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
//texture coordinates
var texCoordBuffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer);
this.gl.enableVertexAttribArray(this.texCoordLocation);
this.gl.vertexAttribPointer(this.texCoordLocation, 2, this.gl.FLOAT, false, 8, 0);
this.gl.bufferData(
this.gl.ARRAY_BUFFER,
new Float32Array([
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
0.0, 1.0,
1.0, 0.0,
1.0, 1.0]),
this.gl.STATIC_DRAW);
//create texture
var texture = this.gl.createTexture();
this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
//normalize image to powers of two
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST);
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST);
//load texture from 2d canvas
this.gl.texImage2D(this.gl.TEXTURE_2D,
0,
this.gl.RGBA,
this.gl.RGBA,
this.gl.UNSIGNED_BYTE,
bufferCanvas);
//load buffer
var buffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);
this.gl.enableVertexAttribArray(this.positionLocation);
this.gl.vertexAttribPointer(this.positionLocation, 2, this.gl.FLOAT, false, 12, 0);
//draw size and position
this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array([
x, y,
x + bufferCanvas.width, y,
x, y + bufferCanvas.height,
x, y + bufferCanvas.height,
x+ bufferCanvas.width, y,
x+ bufferCanvas.width, y + bufferCanvas.height]), this.gl.STATIC_DRAW);
//blur width
this.gl.enableVertexAttribArray(this.width);
this.gl.vertexAttribPointer(this.width, 1, this.gl.FLOAT, false, 12, 8);
//draw
this.gl.drawArrays(this.gl.TRIANGLES, 0, 6);
};
};
var canvas2d = document.getElementById('buffer-canvas');
var context2d = canvas2d.getContext("2d");
var canvasGL = new GL(document.getElementById('main-canvas'));
canvasGL.width = 5.0;
for(var i=0; i<10; i++) {
var r = Math.floor(Math.random() * 255);
var g = Math.floor(Math.random() * 255);
var b = Math.floor(Math.random() * 255);
var a = Math.floor(Math.random() * 255);
context2d.fillStyle = "rgba(" + r + "," + g + "," + b + "," + a + ")";
var x = Math.random() * canvas2d.width;
var y = Math.random() * canvas2d.height;
var width = canvas2d.width - (Math.random() * canvas2d.width);
var height = canvas2d.height - (Math.random() * canvas2d.height);
context2d.fillRect(x, y, width , height);
canvasGL.render(canvas2d, canvas2d.getBoundingClientRect("left"), canvas2d.getBoundingClientRect("top"));
}
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/sylvester/0.1.3/sylvester.min.js"></script>
<script src="https://github.com/mdn/webgl-examples/blob/gh-pages/tutorial/glUtils.js"></script>
<script id="vshader" type="x-shader/x-vertex">
precision mediump float;
attribute vec2 position;
attribute vec2 texcoord;
uniform vec2 resolution;
uniform float width;
varying vec2 texcoord11;
varying vec2 texcoord00;
varying vec2 texcoord02;
varying vec2 texcoord20;
varying vec2 texcoord22;
void main()
{
gl_Position = vec4(((position / resolution) * 2.0 - 1.0) * vec2(1, -1), 0, 1);
// get texcoords
texcoord11 = texcoord;
texcoord00 = texcoord + vec2(-width, -width);
texcoord02 = texcoord + vec2( width, -width);
texcoord20 = texcoord + vec2( width, width);
texcoord22 = texcoord + vec2(-width, width);
}
</script>
<script id="fshader" type="x-shader/x-fragment">
precision mediump float;
uniform sampler2D image;
varying vec2 texcoord11;
varying vec2 texcoord00;
varying vec2 texcoord02;
varying vec2 texcoord20;
varying vec2 texcoord22;
void main()
{
vec4 blur;
blur = texture2D(image, texcoord11);
blur += texture2D(image, texcoord00);
blur += texture2D(image, texcoord02);
blur += texture2D(image, texcoord20);
blur += texture2D(image, texcoord22);
gl_FragColor = 0.2 * blur;
}
</script>
</head>
<body>
<canvas id="main-canvas" width="400" height="300" style="border:1px solid black;"></canvas>
<canvas id="buffer-canvas" width="400" height="300" style="visibility:hidden;"></canvas>
</body>
There's several issues with the code
neither of the script tags seem to be needed
You assign this.width to a uniform location
this.width = this.gl.getUniformLocation(shaderProgram, "width");
But then later destroy that with
canvasGL.width = 5.0
width is a uniform but you're trying to set it as an attribute
wrong
this.gl.enableVertexAttribArray(this.width);
this.gl.vertexAttribPointer(this.width, 1, this.gl.FLOAT, false, 12, 8);
right
this.gl.uniform1f(this.width, whateverYouWantedWidthToBe);
You've got unneeded strides on all your attributes
wrong
this.gl.vertexAttribPointer(this.texCoordLocation, 2, this.gl.FLOAT, false, 8, 0);
this.gl.vertexAttribPointer(this.positionLocation, 2, this.gl.FLOAT, false, 12, 0);
right
this.gl.vertexAttribPointer(this.texCoordLocation, 2, this.gl.FLOAT, false, 0, 0);
this.gl.vertexAttribPointer(this.positionLocation, 2, this.gl.FLOAT, false, 0, 0);
Well I suppose the texcoord one is not wrong if you want to set strides but the position one is wrong since you're using 2 floats per position not 3. Why not just set them to 0?
You're assigning the texCoordLocation to a var but then using it as a property on this
wrong
var texCoordBuffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer);
right?
this.texCoordBuffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer);
On top of that the structure of the code is probably not what you intended.
In WebGL you don't generally call gl.createXXX functions in your render function. You'd call gl.createXXX functions those during initialization. See here for a more typical structure.
It's not clear at all what this line is trying to do
canvasGL.render(canvas2d, canvas2d.getBoundingClientRect("left"),
canvas2d.getBoundingClientRect("top"))
What do you think canvas2d.getBoundingClientRect() is going to return that's useful for rendering? Also that's not how getBoundingClientRect works. It returns a rect, it doesn't take any arguments.
Once you have all that fixed it's not clear what you want width to be. I'm assuming you want it to be pixels but in that case it needs to be 1 / canvas2D.width and you need a separate value for height in your shader since you'll need different values to move up and down a certain number of pixels vs left and right.
other suggestions
Pull gl into a local variable. Then you don't have to do this.gl everywhere. Less typing, shorter, and faster
You can get the contents of script tag with just this
var theSource = shaderScript.text;
You're checking for shader compile errors but not link errors.
visibility: hidden; doesn't do what I think you think it does. You probably want display: none;.
Here's one version that does something.
class GL {
constructor(canvas){
var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
if (!gl) {
alert("Unable to initialize WebGL. Your browser may not support it.");
return;
}
this.gl = gl;
//init shaders
var fragmentShader = getShader(gl, "fshader");
var vertexShader = getShader(gl, "vshader");
var shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert("An error occurred linking the shaders: " + gl.getProgramInfoLog(shaderProgram));
return;
}
this.shaderProgram = shaderProgram;
this.positionLocation = gl.getAttribLocation(shaderProgram, "position");
this.texCoordLocation = gl.getAttribLocation(shaderProgram, "texcoord");
this.resolutionLocation = gl.getUniformLocation(shaderProgram, "resolution");
this.blurOffsetLocation = gl.getUniformLocation(shaderProgram, "blurOffset");
// init texture coordinates
this.texCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordBuffer);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
0.0, 1.0,
1.0, 0.0,
1.0, 1.0]),
gl.STATIC_DRAW);
// create position buffer
this.positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer);
//create texture
this.texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, this.texture);
//normalize image to powers of two
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);
function getShader(gl, id) {
var shaderScript, theSource, currentChild, shader;
shaderScript = document.getElementById(id);
if (!shaderScript) {
return null;
}
var theSource = shaderScript.text;
if (shaderScript.type == "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (shaderScript.type == "x-shader/x-vertex") {
shader = gl.createShader(gl.VERTEX_SHADER);
} else {
// Unknown shader type
return null;
}
gl.shaderSource(shader, theSource);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert("An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader));
return null;
}
return shader;
};
};
render(bufferCanvas, x, y, blurAmount) {
var gl = this.gl;
gl.clear(this.gl.COLOR_BUFFER_BIT);
gl.useProgram(this.shaderProgram);
// setup buffers and attributes
gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordBuffer);
gl.enableVertexAttribArray(this.texCoordLocation);
gl.vertexAttribPointer(this.texCoordLocation, 2, gl.FLOAT, false, 0, 0);
//load buffer
gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer);
gl.enableVertexAttribArray(this.positionLocation);
gl.vertexAttribPointer(this.positionLocation, 2, gl.FLOAT, false, 0, 0);
//draw size and position
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
x, y,
x + bufferCanvas.width, y,
x, y + bufferCanvas.height,
x, y + bufferCanvas.height,
x+ bufferCanvas.width, y,
x+ bufferCanvas.width, y + bufferCanvas.height]), gl.STATIC_DRAW);
//load texture from 2d canvas
gl.bindTexture(gl.TEXTURE_2D, this.texture);
gl.texImage2D(gl.TEXTURE_2D,
0,
gl.RGBA,
gl.RGBA,
gl.UNSIGNED_BYTE,
bufferCanvas);
//blur width
gl.uniform2f(this.blurOffsetLocation,
blurAmount / bufferCanvas.width,
blurAmount / bufferCanvas.height);
//set resolution
gl.uniform2f(this.resolutionLocation,
gl.canvas.width,
gl.canvas.height);
//draw
gl.drawArrays(gl.TRIANGLES, 0, 6);
};
};
var canvas2d = document.getElementById('buffer-canvas');
var context2d = canvas2d.getContext("2d");
var canvasGL = new GL(document.getElementById('main-canvas'));
function render(time) {
time *= 0.001;
var r = Math.floor(Math.random() * 255);
var g = Math.floor(Math.random() * 255);
var b = Math.floor(Math.random() * 255);
var a = Math.floor(Math.random() * 255);
context2d.fillStyle = "rgba(" + r + "," + g + "," + b + "," + a + ")";
var x = Math.random() * canvas2d.width;
var y = Math.random() * canvas2d.height;
var width = canvas2d.width - (Math.random() * canvas2d.width);
var height = canvas2d.height - (Math.random() * canvas2d.height);
context2d.fillRect(x, y, width , height);
canvasGL.render(canvas2d, 0, 0, Math.sin(time) * 5);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
<script id="vshader" type="x-shader/x-vertex">
precision mediump float;
attribute vec2 position;
attribute vec2 texcoord;
uniform vec2 resolution;
uniform vec2 blurOffset;
varying vec2 texcoord11;
varying vec2 texcoord00;
varying vec2 texcoord02;
varying vec2 texcoord20;
varying vec2 texcoord22;
void main()
{
gl_Position = vec4(((position / resolution) * 2.0 - 1.0) * vec2(1, -1), 0, 1);
// get texcoords
texcoord11 = texcoord;
texcoord00 = texcoord + blurOffset * vec2(-1, -1);
texcoord02 = texcoord + blurOffset * vec2( 1, -1);
texcoord20 = texcoord + blurOffset * vec2( 1, 1);
texcoord22 = texcoord + blurOffset * vec2(-1, 1);
}
</script>
<script id="fshader" type="x-shader/x-fragment">
precision mediump float;
uniform sampler2D image;
varying vec2 texcoord11;
varying vec2 texcoord00;
varying vec2 texcoord02;
varying vec2 texcoord20;
varying vec2 texcoord22;
void main()
{
vec4 blur;
blur = texture2D(image, texcoord11);
blur += texture2D(image, texcoord00);
blur += texture2D(image, texcoord02);
blur += texture2D(image, texcoord20);
blur += texture2D(image, texcoord22);
// do you really want to blend the alpha?
gl_FragColor = 0.2 * blur;
}
</script>
<canvas id="main-canvas" width="400" height="300" style="border:1px solid black;"></canvas>
<canvas id="buffer-canvas" width="400" height="300" style="display: none;"></canvas>
Since webGL derives from openGL the coordinate system should be right handed, but as I'm trying to rotate a model by hand-writing the rotation matrix the visual output is the opposite of what I'd expect.
OpenGL needed matrices written in column-major format, and this is the way i'm handwriting a 90° counter-clockwise rotation matrix (assuming we're using a right handed coordinate system) around the y axis:
var rot = new glMatrix.ARRAY_TYPE(16);
//I indexed the array this way to edit those values as if I
were editing rows instead of columns
rot[0] = 0;
rot[4] = 0;
rot[8] = -1;
rot[12] = 0;
rot[1] = 0;
rot[5] = 1;
rot[9] = 0;
rot[13] = 0;
rot[2] = 1;
rot[6] = 0;
rot[10] = 0;
rot[14] = 0;
rot[3] = 0;
rot[7] = 0;
rot[11] = 0;
rot[15] = 1;
gl.uniformMatrix4fv(Program.model, false, rot);
I'd expect a 90° ccw rotation around the y axis but i'm getting a cw one instead. The transpose works as expected which means either the math is wrong or that I'm missing something else
Storage is orthogonal to how matrices are used. You can store them column major or row major. How they get used in multiplication is unrelated to their storage.
To make that clear I could store the matrix like this
// storage format 1
[xaxis-x, xaxis-y, xaxis-z, 0,
yaxis-x, yaxis-y, yaxis-z, 0,
zaxis-x, zazis-y, xaxis-z, 0,
trans-x, trans-y, trans-z, 1]
Or I could store it like this
// storage format 2
[xaxis-x, yaxis-x, zaxis-x, trans-x,
xaxis-y, yaxis-y, zaxis-y, trans-y,
xaxis-z, yazis-z, zaxis-z, trans-z,
0, 0, 0, 1]
I can then write all of these functions
columnMajorMutliplyUsingStorageFormat1(matrix, vector)
columnMajorMutliplyUsingStorageFormat2(matrix, vector)
rowMajorMutliplyUsingStorageFormat1(matrix, vector)
rowMajorMutliplyUsingStorageFormat2(matrix, vector)
You can imagine how those functions would be written. The point is storage format is separate from usage.
In any case pretty much all WebGL and OpenGL programs a matrix almost always takes storage format 1
// storage format 1
[xaxis-x, xaxis-y, xaxis-z, 0,
yaxis-x, yaxis-y, yaxis-z, 0,
zaxis-x, zazis-y, xaxis-z, 0,
trans-x, trans-y, trans-z, 1]
You can verify this in OpenGL 1.0.
glMatrixMode(GL_MODELVIEW);
glTranslatef(1.0f, 2.0f, 3.0f);
float mat[16];
glGetFloatv(GL_MODELVIEW, &mat[0]);
printf("%f %f %f %f\n", mat[0], mat[1], mat[2], mat[3]);
printf("%f %f %f %f\n", mat[4], mat[5], mat[6], mat[7]);
printf("%f %f %f %f\n", mat[8], mat[9], mat[10], mat[11]);
printf("%f %f %f %f\n", mat[12], mat[13], mat[14], mat[15]);
Will print something like
1 0 0 0
0 1 0 0
0 0 1 0
1 2 3 1
In other words if you want to set the translation set elements 12, 13, 14. If you want to set xaxis set elements 0, 1, 2
Here's the typical example with translation in elements 12, 13, 14
The canonical example of usage would be
gl_Position = worldViewProjectionMatrix * position;
"use strict";
twgl.setDefaults({attribPrefix: "a_"});
var m4 = twgl.m4;
var gl = document.getElementById("c").getContext("webgl")
var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
var bufferInfo = twgl.primitives.createCubeBufferInfo(gl, 2);
var tex = twgl.createTexture(gl, {
min: gl.NEAREST,
mag: gl.NEAREST,
src: [
255, 255, 255, 255,
192, 192, 192, 255,
192, 192, 192, 255,
255, 255, 255, 255,
],
});
var uniforms = {
u_lightWorldPos: [1, 8, -10],
u_lightColor: [0.4, 0.8, 0.8, 1],
u_ambient: [0, 0, 0, 1],
u_specular: [1, 1, 1, 1],
u_shininess: 50,
u_specularFactor: 1,
u_diffuse: tex,
};
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
var projection = m4.perspective(30 * Math.PI / 180, gl.canvas.clientWidth / gl.canvas.clientHeight, 0.5, 10);
var eye = [1, 4, -6];
var target = [0, 0, 0];
var up = [0, 1, 0];
var camera = m4.lookAt(eye, target, up);
var view = m4.inverse(camera);
var viewProjection = m4.multiply(view, projection);
var world = m4.rotationY(time);
uniforms.u_viewInverse = camera;
uniforms.u_world = world;
uniforms.u_worldInverseTranspose = m4.transpose(m4.inverse(world));
uniforms.u_worldViewProjection = m4.multiply(world, viewProjection);
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, uniforms);
gl.drawElements(gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script id="vs" type="notjs">
uniform mat4 u_worldViewProjection;
uniform vec3 u_lightWorldPos;
uniform mat4 u_world;
uniform mat4 u_viewInverse;
uniform mat4 u_worldInverseTranspose;
attribute vec4 a_position;
attribute vec3 a_normal;
attribute vec2 a_texcoord;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
void main() {
v_texCoord = a_texcoord;
v_position = (u_worldViewProjection * a_position);
v_normal = (u_worldInverseTranspose * vec4(a_normal, 0)).xyz;
v_surfaceToLight = u_lightWorldPos - (u_world * a_position).xyz;
v_surfaceToView = (u_viewInverse[3] - (u_world * a_position)).xyz;
gl_Position = v_position;
}
</script>
<script id="fs" type="notjs">
precision mediump float;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
uniform vec4 u_lightColor;
uniform vec4 u_ambient;
uniform sampler2D u_diffuse;
uniform vec4 u_specular;
uniform float u_shininess;
uniform float u_specularFactor;
vec4 lit(float l ,float h, float m) {
return vec4(1.0,
max(l, 0.0),
(l > 0.0) ? pow(max(0.0, h), m) : 0.0,
1.0);
}
void main() {
vec4 diffuseColor = texture2D(u_diffuse, v_texCoord);
vec3 a_normal = normalize(v_normal);
vec3 surfaceToLight = normalize(v_surfaceToLight);
vec3 surfaceToView = normalize(v_surfaceToView);
vec3 halfVector = normalize(surfaceToLight + surfaceToView);
vec4 litR = lit(dot(a_normal, surfaceToLight),
dot(a_normal, halfVector), u_shininess);
vec4 outColor = vec4((
u_lightColor * (diffuseColor * litR.y + diffuseColor * u_ambient +
u_specular * litR.z * u_specularFactor)).rgb,
diffuseColor.a);
gl_FragColor = outColor;
}
</script>
<script src="https://twgljs.org/dist/twgl-full.min.js"></script>
<canvas id="c"></canvas>
and here's the opposite example with translation in elements 3, 7, 11
In this case the math in the shader has been changed to
gl_Position = position * worldViewProjectionMatrix;
"use strict";
twgl.setDefaults({attribPrefix: "a_"});
var m4 = twgl.m4;
var gl = document.getElementById("c").getContext("webgl")
var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
var bufferInfo = twgl.primitives.createCubeBufferInfo(gl, 2);
var tex = twgl.createTexture(gl, {
min: gl.NEAREST,
mag: gl.NEAREST,
src: [
255, 255, 255, 255,
192, 192, 192, 255,
192, 192, 192, 255,
255, 255, 255, 255,
],
});
var uniforms = {
u_lightWorldPos: [1, 8, -10],
u_lightColor: [0.4, 0.8, 0.8, 1],
u_ambient: [0, 0, 0, 1],
u_specular: [1, 1, 1, 1],
u_shininess: 50,
u_specularFactor: 1,
u_diffuse: tex,
};
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
var projection = m4.perspective(30 * Math.PI / 180, gl.canvas.clientWidth / gl.canvas.clientHeight, 0.5, 10);
var eye = [1, 4, -6];
var target = [0, 0, 0];
var up = [0, 1, 0];
var camera = m4.lookAt(eye, target, up);
var view = m4.inverse(camera);
var viewProjection = m4.multiply(view, projection);
var world = m4.rotationY(time);
uniforms.u_viewInverse = m4.transpose(camera);
uniforms.u_world = m4.transpose(world);
uniforms.u_worldInverseTranspose = m4.transpose(m4.transpose(m4.inverse(world)));
uniforms.u_worldViewProjection = m4.transpose(m4.multiply(world, viewProjection));
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, uniforms);
gl.drawElements(gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script id="vs" type="notjs">
uniform mat4 u_worldViewProjection;
uniform vec3 u_lightWorldPos;
uniform mat4 u_world;
uniform mat4 u_viewInverse;
uniform mat4 u_worldInverseTranspose;
attribute vec4 a_position;
attribute vec3 a_normal;
attribute vec2 a_texcoord;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
void main() {
v_texCoord = a_texcoord;
v_position = (a_position * u_worldViewProjection);
v_normal = (vec4(a_normal, 0) * u_worldInverseTranspose).xyz;
v_surfaceToLight = u_lightWorldPos - (a_position * u_world).xyz;
v_surfaceToView = (u_viewInverse[3] - (a_position * u_world)).xyz;
gl_Position = v_position;
}
</script>
<script id="fs" type="notjs">
precision mediump float;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
uniform vec4 u_lightColor;
uniform vec4 u_ambient;
uniform sampler2D u_diffuse;
uniform vec4 u_specular;
uniform float u_shininess;
uniform float u_specularFactor;
vec4 lit(float l ,float h, float m) {
return vec4(1.0,
max(l, 0.0),
(l > 0.0) ? pow(max(0.0, h), m) : 0.0,
1.0);
}
void main() {
vec4 diffuseColor = texture2D(u_diffuse, v_texCoord);
vec3 a_normal = normalize(v_normal);
vec3 surfaceToLight = normalize(v_surfaceToLight);
vec3 surfaceToView = normalize(v_surfaceToView);
vec3 halfVector = normalize(surfaceToLight + surfaceToView);
vec4 litR = lit(dot(a_normal, surfaceToLight),
dot(a_normal, halfVector), u_shininess);
vec4 outColor = vec4((
u_lightColor * (diffuseColor * litR.y + diffuseColor * u_ambient +
u_specular * litR.z * u_specularFactor)).rgb,
diffuseColor.a);
gl_FragColor = outColor;
}
</script>
<script src="https://twgljs.org/dist/twgl-full.min.js"></script>
<canvas id="c"></canvas>
code:
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LESS);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
Problem in that on a figure "superfluous" is drawn:
how to correct it?
P.S. alpha=0.9
What is your perspective zNear and zFar set to? Is it possible you're setting it too close and the back of your cube is being clipped? See sample below where it's set too close. That doesn't look like your issue but it's hard to tell.
Also are you sorting your polygons? When rendering transparent things you generally have to draw front to back. For a convex object like a sphere, pyramid, or cube you can draw twice with culling on, first with gl.cullFace(gl.FRONT) to draw only the backfacing triangles, the ones further from the camera, and then again with gl.cullFace(gl.BACK) to draw only the front facing triangles, the ones closer to the camera.
Yet another issue is are you correctly providing premultiplied alpha to the canvas? Most shaders do this
gl_FragColor = someUnpremultipliedAlphaColor;
But by default you need to provide pre-multiplied alpha colors
gl_FragColor = vec4(color.rgb * color.a, color.a);
Or you can set the canvas to use un-premultiplied colors
gl = someCanvas.getContext("webgl", { premultipliedAlpha: false });
window.onload = function() {
// Get A WebGL context
var canvas = document.getElementById("c");
var gl = canvas.getContext("webgl");
if (!gl) {
return;
}
var programInfo = webglUtils.createProgramInfo(gl, ["vs", "fs"]);
var createFlattenedVertices = function(gl, vertices) {
return webglUtils.createBufferInfoFromArrays(
gl,
primitives.makeRandomVertexColors(
primitives.deindexVertices(vertices),
{
vertsPerColor: 6,
rand: function(ndx, channel) {
return channel < 3 ? ((128 + Math.random() * 128) | 0) : 255;
}
})
);
};
var bufferInfo = createFlattenedVertices(gl, primitives.createCubeVertices(1));
function degToRad(d) {
return d * Math.PI / 180;
}
var cameraAngleRadians = degToRad(0);
var fieldOfViewRadians = degToRad(60);
var uniforms = {
u_color: [1, 1, 1, 0.8],
u_matrix: null,
};
var zClose = false;
var zNear = 1;
var zFar = 3;
var zElem = document.getElementById("z");
var bElem = document.getElementById("b");
bElem.addEventListener('click', toggleZDepth, false);
toggleZDepth();
function toggleZDepth() {
zClose = !zClose;
zFar = zClose ? 3.5 : 4;
zElem.innerHTML = zFar;
}
function drawScene() {
gl.enable(gl.CULL_FACE);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
var aspect = canvas.clientWidth / canvas.clientHeight;
var projectionMatrix =
makePerspective(fieldOfViewRadians, aspect, zNear, zFar);
var time = Date.now() * 0.0005;
var radius = 3;
var cameraPosition = [Math.cos(time) * radius, 1, Math.sin(time) * radius];
var target = [0, 0, 0];
var up = [0, 1, 0];
var cameraMatrix = makeLookAt(cameraPosition, target, up);
var viewMatrix = makeInverse(cameraMatrix);
uniforms.u_matrix = matrixMultiply(viewMatrix, projectionMatrix);
gl.useProgram(programInfo.program);
webglUtils.setBuffersAndAttributes(gl, programInfo.attribSetters, bufferInfo);
webglUtils.setUniforms(programInfo.uniformSetters, uniforms);
// draw back facing polygons first
gl.cullFace(gl.FRONT);
gl.drawArrays(gl.TRIANGLES, 0, bufferInfo.numElements);
// now draw front facing polygons
gl.cullFace(gl.BACK);
gl.drawArrays(gl.TRIANGLES, 0, bufferInfo.numElements);
requestAnimationFrame(drawScene);
}
drawScene();
}
canvas {
border: 1px solid black;
}
#overlay {
position: absolute;
top: 20px;
left: 20px;
z-index: 2;
}
<script src="//webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
<script src="//webglfundamentals.org/webgl/resources/webgl-3d-math.js"></script>
<script src="//webglfundamentals.org/webgl/resources/primitives.js"></script>
<canvas id="c" width="400" height="200"></canvas>
<div id="overlay">
<button id="b">toggle z-far</button>
<div>z-far = <span id="z"></span></div>
</div>
<!-- vertex shader -->
<script id="vs" type="x-shader/x-vertex">
attribute vec4 a_position;
attribute vec4 a_color;
varying vec4 v_color;
uniform mat4 u_matrix;
void main() {
gl_Position = u_matrix * a_position;
v_color = a_color;
}
</script>
<!-- fragment shader -->
<script id="fs" type="x-shader/x-fragment">
precision mediump float;
uniform vec4 u_color;
varying vec4 v_color;
void main() {
vec4 color = v_color * u_color;
gl_FragColor = vec4(color.rgb * color.a, color.a); // premultiply color
}
</script>