webgl lighting shader working with firefox but not with chrome - google-chrome

I am currently developing a 3D engine using WebGL, and I encountered a problem with the lighting shader. I am using an array of structure in order to pass the light parameters to the vertex and fragment shaders (with the same array passed as "uniform"). It actualy works very well with Firefox 35, but not at all with Google chrome and I have no idea what is wrong with this code.
Here is the code for the vertex shader:
precision mediump float;
precision mediump int;
struct light{
vec3 ambient;
vec3 specular;
vec3 diffuse;
vec3 vector;
};
attribute vec3 aVertexPosition;
attribute vec4 aVertexColor;
attribute vec3 aVertexNormal;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
uniform mat4 uNormalMatrix;
varying vec4 vMaterialColor;
varying vec3 N;
varying vec3 V;
const int MAX_LIGHT = 3;
uniform light lights[MAX_LIGHT];
varying vec3 L[MAX_LIGHT];
uniform int lightCount;
void main(void) {
vec4 position4 = uMVMatrix * vec4(aVertexPosition, 1.0);
vec3 position = position4.xyz / position4.w;
V = normalize(-position);
for(int i = 0 ; i < MAX_LIGHT ; i++){
if (i < lightCount){
L[i] = normalize(lights[i].vector - position);
}
}
N = normalize(vec3(uNormalMatrix * vec4(aVertexNormal,0.0)));
gl_Position = uPMatrix * position4;
vMaterialColor = aVertexColor;
}
And the code for the fragment shader is:
precision mediump float;
precision mediump int;
varying vec3 N;
varying vec3 V;
struct materialProperties {
vec3 ambient;
vec3 diffuse;
vec3 specular;
vec3 emissive;
float shininess;
};
struct light{
vec3 ambient;
vec3 specular;
vec3 diffuse;
vec3 vector;
};
// Material
varying vec4 vMaterialColor;
uniform materialProperties uMaterial;
// Light
const int MAX_LIGHT = 3;
uniform light lights[MAX_LIGHT];
varying vec3 L[MAX_LIGHT];
uniform int lightCount;
void main(void) {
vec3 color = vec3(0.0);
for(int i = 0; i < MAX_LIGHT ; i++){
if(i < lightCount){
float dot_LN = max(dot(N,L[i]), 0.0);
float specular = 0.0;
if(dot_LN > 0.0){
vec3 H = normalize(L[i]+V);
float dot_HN = max(dot(H, N), 0.0);
specular = pow(dot_HN, uMaterial.shininess);
}
vec3 ambiantComponent = uMaterial.ambient * lights[i].ambient;
vec3 diffuseComponent = uMaterial.diffuse * vMaterialColor.xyz * dot_LN * lights[i].diffuse;
vec3 specularComponent = uMaterial.specular * specular * lights[i].specular;
color += ambiantComponent + diffuseComponent + specularComponent;
}
}
gl_FragColor = vec4(color, vMaterialColor.w);
}
I am sending the light informations with gl.uniformxx:
for(var i = 0 ; i < light.length ; i++){
gl.uniform3fv(shaderProgram.light[i].vector, light[i]._lightVector);
gl.uniform3fv(shaderProgram.light[i].ambient, light[i].ambient);
gl.uniform3fv(shaderProgram.light[i].specular, light[i].specular);
gl.uniform3fv(shaderProgram.light[i].diffuse, light[i].diffuse);
}
gl.uniform1i(shaderProgram.lightCount, light.length);
And shaderProgram is declared with the following code:
shaderProgram.light = [];
for(var i = 0 ; i < 3 ; i++){
var l = {};
l.ambient = gl.getUniformLocation(shaderProgram,"lights["+i+"].ambient");
l.specular = gl.getUniformLocation(shaderProgram,"lights["+i+"].specular");
l.vector = gl.getUniformLocation(shaderProgram,"lights["+i+"].vector");
l.diffuse = gl.getUniformLocation(shaderProgram,"lights["+i+"].diffuse");
shaderProgram.light.push(l);
}
shaderProgram.lightCount = gl.getUniformLocation(shaderProgram,"lightCount");
The rendered scene with Google Chrome is a black image, instead of Firefox which renders the scene as expected.
Do you know where is the problem in this code?
Edit:
The following minimal code presents the same bug:
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Fail</title>
</head>
<script id="shader-fs" type="x-shader/x-fragment">
precision mediump float;
precision mediump int;
struct test_t{
vec3 a;
vec3 b;
vec3 c;
vec3 d;
};
uniform test_t test;
varying vec3 sum;
void main(void) {
gl_FragColor = vec4(test.a+test.b+test.c-sum, 1.0);
}
</script>
<script id="shader-vs" type="x-shader/x-vertex">
precision mediump float;
precision mediump int;
struct test_t{
vec3 a;
vec3 b;
vec3 c;
vec3 d;
};
attribute vec3 aVertexPosition;
uniform test_t test;
varying vec3 sum;
void main(void) {
sum = test.a + test.b;
gl_Position = vec4(aVertexPosition,1.0);
}
</script>
<script type="application/javascript">
var canvas, gl, shaderProgram, triangleVertexPositionBuffer;
function start() {
canvas = document.getElementById("webgl-canvas");
initWebGL(canvas); // Initialize the GL context
if (gl) {
gl.clearColor(0.0, 0.0, 0.0, 1.0); // Clear to black, fully opaque
gl.clearDepth(1.0); // Clear everything
gl.enable(gl.DEPTH_TEST); // Enable depth testing
gl.depthFunc(gl.LEQUAL); // Near things obscure far things
initShaders();
triangleVertexPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
var vertices = [
0.0, 0.5, -0.5,
-0.5, -0.5, -0.5,
0.5, -0.5, -0.5
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
triangleVertexPositionBuffer.itemSize = 3;
triangleVertexPositionBuffer.numItems = 3;
setInterval(drawScene, 15);
}
}
function initWebGL() {
gl = null;
try {
gl = canvas.getContext("experimental-webgl");
}
catch(e) {
}
if (!gl)
alert("Unable to initialize WebGL");
}
function initShaders() {
var fragmentShader = getShader(gl, "shader-fs");
var vertexShader = getShader(gl, "shader-vs");
shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert("Unable to initialize the shader program.");
}
gl.useProgram(shaderProgram);
vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
gl.enableVertexAttribArray(vertexPositionAttribute);
shaderProgram.test = {}
shaderProgram.test.a = gl.getUniformLocation(shaderProgram,"test.a");
shaderProgram.test.b = gl.getUniformLocation(shaderProgram,"test.b");
shaderProgram.test.c = gl.getUniformLocation(shaderProgram,"test.c");
shaderProgram.test.d = gl.getUniformLocation(shaderProgram,"test.d");
}
function getShader(gl, id) {
var shaderScript = document.getElementById(id);
if (!shaderScript) {
return null;
}
var theSource = "";
var currentChild = shaderScript.firstChild;
while(currentChild) {
if (currentChild.nodeType == 3) {
theSource += currentChild.textContent;
}
currentChild = currentChild.nextSibling;
}
var shader;
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 {
return null; // Unknown shader type
}
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;
}
function drawScene() {
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.uniform3fv(shaderProgram.test.a, [1.0,0.0,0.0]);
gl.uniform3fv(shaderProgram.test.b, [0.0,1.0,0.0]);
gl.uniform3fv(shaderProgram.test.c, [0.0,0.0,1.0]);
gl.uniform3fv(shaderProgram.test.d, [1.0,1.0,1.0]);
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.TRIANGLES, 0, triangleVertexPositionBuffer.numItems);
}
</script>
<body onload="start()">
<canvas id="webgl-canvas" width="500" height="500"></canvas>
</body>
</html>
The triangle is yellow/white and blinking on Google Chrome, and blue on Mozilla firefox. The problem seems to be specific to windows platform (tested with windows 8.1 x64).
Edit: The issue has been reported to the chromium team, the fix should be developped soon.

I tested your minimal code with win7 Chrome and IE11 and win8 Chrome and IE11. And as you said, it is not working with win8 Chrome (but works with win7 Chrome).
I did a few modifications to find out what's wrong. In both vertex and fragment shader I see uniform test_t test has only property a = [1,0,0], but properties b, c, d seems to be [0,0,0], (or not being initialized so whatever values).
There are two tests:
https://www.khronos.org/registry/webgl/sdk/tests/conformance/glsl/misc/shader-with-array-of-structs-uniform.html
https://www.khronos.org/registry/webgl/sdk/tests/conformance/glsl/misc/shader-with-array-of-structs-containing-arrays.html
but both are passing in my chrome. It might be because they don't cover every possible setup and theirs target is vec4 only.
Temporary solution you can do (and is working for me) is to use vec4 instead of vec3. Or wait till the Chrome team fixes it.

Related

Fragment Shader Draw Unexpected Border Line

I have a shader program which will make a border line depends on alpha value arround each pixels. I hope to add a yellow border line along an image just like this:
However, it does not give me the expected answer. The thing that I cant understand the most is why there will always be a border line at one boundary side of image.
My fragment shader codes:
varying vec4 v_color;
varying vec2 v_texCoords;
uniform sampler2D u_texture;
uniform vec2 u_imageSize;
uniform vec4 u_borderColor;
uniform float u_borderSize;
void main() {
vec4 color = texture2D(u_texture, v_texCoords);
vec2 pixelToTextureCoords = 1. / u_imageSize;
bool isInteriorPoint = true;
bool isExteriorPoint = true;
for (float dx = -u_borderSize; dx < u_borderSize; dx++)
{
for (float dy = -u_borderSize; dy < u_borderSize; dy++){
vec2 point = v_texCoords + vec2(dx,dy) * pixelToTextureCoords;
float alpha = texture2D(u_texture, point).a;
if ( alpha < 0.5 )
isInteriorPoint = false;
if ( alpha > 0.5 )
isExteriorPoint = false;
}
}
if (!isInteriorPoint && !isExteriorPoint && color.a < 0.5)
gl_FragColor = u_borderColor;
else
gl_FragColor = v_color * color;
}
My vertex shader codes:
attribute vec4 a_position;
attribute vec4 a_color;
attribute vec2 a_texCoord0;
varying vec4 v_color;
varying vec2 v_texCoords;
uniform mat4 u_projTrans;
uniform vec2 u_viewportInverse;
void main() {
v_color = a_color;
v_texCoords = a_texCoord0;
gl_Position = u_projTrans * a_position;
}
My definition codes:
shaderProgram.setUniformf( "u_imageSize", new Vector2(getWidth(), getHeight()) );
shaderProgram.setUniformf( "u_borderColor", Color.YELLOW );
shaderProgram.setUniformf( "u_borderSize", 1 );
Outcome image(shape above is without shader and shape below is with shader):
Please provide me any kind of guides.
The issue is caused, because the texture coordinates in the loop become < 0.0 respectively > 1.0. So the texture is looked up "out of bounds". What happens in this case depends on the wrap parameters (see glTexParameter. Add a range check to the loop and skip the lookup when coordinates are not in range [0.0, 1.0], to solve the issue:
for (float dx = -u_borderSize; dx < u_borderSize; dx++)
{
for (float dy = -u_borderSize; dy < u_borderSize; dy++){
vec2 point = v_texCoords + vec2(dx,dy) * pixelToTextureCoords;
// range check
if (point.x < 0.0 || point.x > 1.0 || point.y < 0.0 || point.y > 1.0)
continue;
float alpha = texture2D(u_texture, point).a;
if ( alpha < 0.5 )
isInteriorPoint = false;
if ( alpha > 0.5 )
isExteriorPoint = false;
}
}

Shaders draw unexpected transparent background

I have a new class MyActor extended Actor, with applying shader into it.
However, the shader will fill up the transparent background unexpectedly.
draw() method codes inside MyActor as follow:
#Override
public void draw(Batch batch, float parentAlpha) {
if(shaderProgram!=null)
{
batch.setShader(shaderProgram);
}
if(!drawParentAtBack)super.draw(batch, parentAlpha); // by default is false
Color c = getColor(); // used to apply tint color effect
batch.setColor(c.r, c.g, c.b, c.a * parentAlpha);
if ( isVisible() )
{
if(displayFrame !=null) // a textureregion
{
batch.draw(displayFrame,
getX(),getY(),
getOriginX(),getOriginY(),
getWidth(),getHeight(),
getScaleX(),getScaleY(),
getRotation());
}
}
if(drawParentAtBack)super.draw(batch, parentAlpha);
if(shaderProgram!=null)
{
batch.setShader(null);
}
}
public void setShader(String vs, String fs){
vertexShaderCode = Gdx.files.internal("shaders/" + vs + ".vs").readString();
fragmentShaderCode = Gdx.files.internal("shaders/" + fs + ".fs").readString();
shaderProgram = new ShaderProgram(vertexShaderCode, fragmentShaderCode);
if (!shaderProgram.isCompiled())
{
d( "Shader compile error: " + shaderProgram.getLog() );
}
}
My definition goes like this:
MyActor myActor1;myActor2;
..... // setting up myActor1 & myActor2
myActor1.setShader("default","greyScale");
myActor2.setShader("default","greyScale");
my simple shaderCodes from this tutorial:
#ifdef GL_ES
precision mediump float;
#endif
varying vec4 v_color;
varying vec2 v_texCoords;
uniform sampler2D u_texture;
uniform mat4 u_projTrans;
void main() {
vec3 color = texture2D(u_texture, v_texCoords).rgb;
float gray = (color.r + color.g + color.b) / 3.0;
vec3 grayscale = vec3(gray);
gl_FragColor = vec4(grayscale, 1.0);
}
My vertex shader codes from this tutorial:
#ifdef GL_ES
precision mediump float;
#endif
varying vec4 v_color;
varying vec2 v_texCoords;
uniform sampler2D u_texture;
uniform mat4 u_projTrans;
void main() {
vec3 color = texture2D(u_texture, v_texCoords).rgb;
float gray = (color.r + color.g + color.b) / 3.0;
vec3 grayscale = vec3(gray);
gl_FragColor = vec4(grayscale, 1.0);
}
My expected result images are gray color shapes with transparent background, BUT it turns out like this:
sample shape image without shader:
Any helps please.
In general transparency is achieved Alpha Blending. The alpha channel of the fragment controls the transparency and has to be set.
In the fragment shader the alpha channel of the texture is omitted:
gl_FragColor = vec4(grayscale, 1.0);
Set the alpha channel of the texture (u_texture) to the alpha channel of the output (gl_FragColor.a):
#ifdef GL_ES
precision mediump float;
#endif
varying vec4 v_color;
varying vec2 v_texCoords;
uniform sampler2D u_texture;
uniform mat4 u_projTrans;
void main() {
// read RGB color channels and alpha channel
vec4 color = texture2D(u_texture, v_texCoords);
float gray = (color.r + color.g + color.b) / 3.0;
vec3 grayscale = vec3(gray);
// write gray scale and alpha channel
gl_FragColor = vec4(grayscale.rgb, color.a);
}

WebGL – Use mesh as mask for background image

I have the following problem to solve with WebGL:
Imagine a mesh in front of the camera. The mesh is not actually shaded but its "silhouette" as a whole is used to reveal a background image as some sort of mask or stencil if you will. So e.g. you would have an output image with a white background and a silhouette-shape filled with a texture/image being the background at that position.
(The meshes I want to use are obviously more complex than a sphere)
What is the best way to achieve such an effect? I thought about either projection mapping as to sort of projecting the background texture onto the mesh from the cameras perspective. Or is maybe a stencil buffer the way to go? From what I've read, the support of it is not so high at this point. Maybe there is also a way simpler method to solve this issue that I've missed?
There tons of ways to do this, which is best for you is up to you.
Use the stencil buffer
Draw your mesh into the stencil buffer then draw your image with the stencil test set so it only draws where the mesh was drawn
var geoVS = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
gl_Position = matrix * position;
}
`;
var geoFS = `
precision mediump float;
void main() {
gl_FragColor = vec4(1, 0, 0, 1); // doesn't matter. We're only using the stencil
}
`;
var imgVS = `
attribute vec4 position;
varying vec2 v_texcoord;
void main() {
gl_Position = position;
v_texcoord = position.xy * .5 + .5; // only works if position is -1 <-> +1 quad
}
`;
var imgFS = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D tex;
void main() {
gl_FragColor = texture2D(tex, v_texcoord);
}
`;
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl", {stencil: true});
const geoPrgInfo = twgl.createProgramInfo(gl, [geoVS, geoFS]);
const imgPrgInfo = twgl.createProgramInfo(gl, [imgVS, imgFS]);
const geoBufferInfo = twgl.primitives.createCubeBufferInfo(gl, 1);
const quadBufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);
const tex = twgl.createTexture(gl, {
src: "https://farm9.staticflickr.com/8873/18598400202_3af67ef38f_z_d.jpg",
crossOrigin: "",
flipY: true,
});
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
var fov = Math.PI * .25;
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
var zNear = 0.1;
var zFar = 10;
var mat = m4.perspective(fov, aspect, zNear, zFar);
mat = m4.translate(mat, [0, 0, -3]);
mat = m4.rotateX(mat, time * 0.81);
mat = m4.rotateZ(mat, time * 0.77);
// draw geometry to generate stencil
gl.useProgram(geoPrgInfo.program);
twgl.setBuffersAndAttributes(gl, geoPrgInfo, geoBufferInfo);
twgl.setUniforms(geoPrgInfo, {
matrix: mat,
});
// write 1 to stencil
gl.enable(gl.STENCIL_TEST);
gl.stencilFunc(gl.ALWAYS, 1, 0xFF);
gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE);
gl.drawElements(gl.TRIANGLES, geoBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
// draw image where stencil is set
gl.useProgram(imgPrgInfo.program);
twgl.setBuffersAndAttributes(gl, imgPrgInfo, quadBufferInfo);
twgl.setUniforms(imgPrgInfo, {
tex: tex,
});
gl.stencilFunc(gl.EQUAL, 1, 0xFF);
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
gl.drawElements(gl.TRIANGLES, quadBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
<canvas></canvas>
Use the depth buffer
Draw your mesh into the depth buffer then draw your image with the depth function set so it only draws where the mesh was drawn.
var geoVS = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
gl_Position = matrix * position;
}
`;
var geoFS = `
precision mediump float;
void main() {
gl_FragColor = vec4(1, 0, 0, 1); // doesn't matter. We're only using the stencil
}
`;
var imgVS = `
attribute vec4 position;
varying vec2 v_texcoord;
void main() {
gl_Position = position;
v_texcoord = position.xy * .5 + .5; // only works if position is -1 <-> +1 quad
}
`;
var imgFS = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D tex;
void main() {
gl_FragColor = texture2D(tex, v_texcoord);
}
`;
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl", {stencil: true});
const geoPrgInfo = twgl.createProgramInfo(gl, [geoVS, geoFS]);
const imgPrgInfo = twgl.createProgramInfo(gl, [imgVS, imgFS]);
const geoBufferInfo = twgl.primitives.createCubeBufferInfo(gl, 1);
const quadBufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);
const tex = twgl.createTexture(gl, {
src: "https://farm9.staticflickr.com/8873/18598400202_3af67ef38f_z_d.jpg",
crossOrigin: "",
flipY: true,
});
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearDepth(0); // clear depth to 0 (normally it's 1)
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.enable(gl.DEPTH_TEST);
var fov = Math.PI * .25;
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
var zNear = 0.1;
var zFar = 10;
var mat = m4.perspective(fov, aspect, zNear, zFar);
mat = m4.translate(mat, [0, 0, -3]);
mat = m4.rotateX(mat, time * 0.81);
mat = m4.rotateZ(mat, time * 0.77);
// draw geometry to generate depth
gl.useProgram(geoPrgInfo.program);
twgl.setBuffersAndAttributes(gl, geoPrgInfo, geoBufferInfo);
twgl.setUniforms(geoPrgInfo, {
matrix: mat,
});
gl.depthFunc(gl.ALWAYS); // we only care about silhouette
gl.drawElements(gl.TRIANGLES, geoBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
// draw image where depth is set
gl.useProgram(imgPrgInfo.program);
twgl.setBuffersAndAttributes(gl, imgPrgInfo, quadBufferInfo);
twgl.setUniforms(imgPrgInfo, {
tex: tex,
});
gl.depthFunc(gl.LESS);
// this quad is drawn at z = 0 which is in the middle Z wize. Should probably
// make it 1 so it's in the back but it's working as is so too lazy to
// change
gl.drawElements(gl.TRIANGLES, quadBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
<canvas></canvas>
Use CSS
Set the canvas's CSS background to an image. Clear the canvas to some color, draw your mesh with 0,0,0,0 to cut a hole.
var geoVS = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
gl_Position = matrix * position;
}
`;
var geoFS = `
precision mediump float;
void main() {
gl_FragColor = vec4(0);
}
`;
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl", {stencil: true});
const geoPrgInfo = twgl.createProgramInfo(gl, [geoVS, geoFS]);
const geoBufferInfo = twgl.primitives.createCubeBufferInfo(gl, 1);
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearColor(1, 1, 1, 1); // clear to white
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
var fov = Math.PI * .25;
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
var zNear = 0.1;
var zFar = 10;
var mat = m4.perspective(fov, aspect, zNear, zFar);
mat = m4.translate(mat, [0, 0, -3]);
mat = m4.rotateX(mat, time * 0.81);
mat = m4.rotateZ(mat, time * 0.77);
// draw in 0,0,0,0 to cut a whole in the canvas to the HTML/CSS
// defined background
gl.useProgram(geoPrgInfo.program);
twgl.setBuffersAndAttributes(gl, geoPrgInfo, geoBufferInfo);
twgl.setUniforms(geoPrgInfo, {
matrix: mat,
});
gl.drawElements(gl.TRIANGLES, geoBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block;
background-image: url(https://farm9.staticflickr.com/8873/18598400202_3af67ef38f_z_d.jpg);
background-size: 100% 100%;
}
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
<canvas></canvas>
Generate a texture mask
Draw the mesh to a texture through framebuffer to generate a silhouette in the texture. Use that texture as input to another shader as a mask
var geoVS = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
gl_Position = matrix * position;
}
`;
var geoFS = `
precision mediump float;
void main() {
gl_FragColor = vec4(1);
}
`;
var imgVS = `
attribute vec4 position;
varying vec2 v_texcoord;
void main() {
gl_Position = position;
v_texcoord = position.xy * .5 + .5; // only works if position is -1 <-> +1 quad
}
`;
var imgFS = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D colorTex;
uniform sampler2D maskTex;
void main() {
vec4 color = texture2D(colorTex, v_texcoord);
vec4 mask = texture2D(maskTex, v_texcoord);
gl_FragColor = color * mask;
}
`;
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl", {stencil: true});
const geoPrgInfo = twgl.createProgramInfo(gl, [geoVS, geoFS]);
const imgPrgInfo = twgl.createProgramInfo(gl, [imgVS, imgFS]);
const geoBufferInfo = twgl.primitives.createCubeBufferInfo(gl, 1);
const quadBufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);
const tex = twgl.createTexture(gl, {
src: "https://farm9.staticflickr.com/8873/18598400202_3af67ef38f_z_d.jpg",
crossOrigin: "",
flipY: true,
});
// with no options creates a framebuffer with an RGBA8 texture
// and depth buffer
const fbi = twgl.createFramebufferInfo(gl);
function render(time) {
time *= 0.001;
if (twgl.resizeCanvasToDisplaySize(gl.canvas)) {
// with no argument will resize to the canvas size
twgl.resizeFramebufferInfo(gl, fbi);
}
// calls gl.bindFramebuffer and gl.viewport
twgl.bindFramebufferInfo(gl, fbi);
// first draw the geometry to the texture
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
var fov = Math.PI * .25;
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
var zNear = 0.1;
var zFar = 10;
var mat = m4.perspective(fov, aspect, zNear, zFar);
mat = m4.translate(mat, [0, 0, -3]);
mat = m4.rotateX(mat, time * 0.81);
mat = m4.rotateZ(mat, time * 0.77);
gl.useProgram(geoPrgInfo.program);
twgl.setBuffersAndAttributes(gl, geoPrgInfo, geoBufferInfo);
twgl.setUniforms(geoPrgInfo, {
matrix: mat,
});
gl.drawElements(gl.TRIANGLES, geoBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
// the texture now is black (0,0,0,0) where there's nothing and (1,1,1,1)
// where are geometry was drawn
// calls gl.bindFramebuffer and gl.viewport
twgl.bindFramebufferInfo(gl, null);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
// draw image using our texture as a mask
gl.useProgram(imgPrgInfo.program);
twgl.setBuffersAndAttributes(gl, imgPrgInfo, quadBufferInfo);
twgl.setUniforms(imgPrgInfo, {
colorTex: tex,
maskTex: fbi.attachments[0],
});
gl.drawElements(gl.TRIANGLES, quadBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
<canvas></canvas>
Personally I'd probably use the last one as it's more flexible. You can use any technique to generate the mask. The mask will have levels (as in you can set it to 0.5 to get a 50/50 blend). That means you can get an antialiased each if you want. You can mask each color separate amounts. You could easily blend between 2 images, etc. You could add other effects like displacement maps etc to the final pass.
Here's an example of rendering a cube in shades of gray and using the result to blend 2 images.
var geoVS = `
attribute vec4 position;
attribute vec3 normal;
uniform mat4 matrix;
varying vec3 v_normal;
void main() {
gl_Position = matrix * position;
v_normal = (matrix * vec4(normal, 0)).xyz;
}
`;
var geoFS = `
precision mediump float;
uniform vec3 u_lightDir;
varying vec3 v_normal;
void main() {
gl_FragColor = vec4(dot(normalize(v_normal), u_lightDir) * .5 + .5);
}
`;
var imgVS = `
attribute vec4 position;
varying vec2 v_texcoord;
void main() {
gl_Position = position;
v_texcoord = position.xy * .5 + .5; // only works if position is -1 <-> +1 quad
}
`;
var imgFS = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D color1Tex;
uniform sampler2D color2Tex;
uniform sampler2D maskTex;
void main() {
// it probably doesn't make sense to use the same
// texcoords for all 3 textures but I'm lazy
vec4 color1 = texture2D(color1Tex, v_texcoord);
vec4 color2 = texture2D(color2Tex, v_texcoord);
vec4 mask = texture2D(maskTex, v_texcoord);
gl_FragColor = mix(color1, color2, mask);
}
`;
const m4 = twgl.m4;
const v3 = twgl.v3;
const gl = document.querySelector("canvas").getContext("webgl", {stencil: true});
const geoPrgInfo = twgl.createProgramInfo(gl, [geoVS, geoFS]);
const imgPrgInfo = twgl.createProgramInfo(gl, [imgVS, imgFS]);
const geoBufferInfo = twgl.primitives.createCubeBufferInfo(gl, 1);
const quadBufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);
const textures = twgl.createTextures(gl, {
tex1: {
src: "https://farm9.staticflickr.com/8873/18598400202_3af67ef38f_z_d.jpg",
crossOrigin: "",
flipY: true,
},
tex2: {
src: "https://farm1.staticflickr.com/339/18414821420_e3d0a8ec5f_z_d.jpg",
crossOrigin: "",
flipY: true,
},
});
// with no options creates a framebuffer with an RGBA8 texture
// and depth buffer
const fbi = twgl.createFramebufferInfo(gl);
function render(time) {
time *= 0.001;
if (twgl.resizeCanvasToDisplaySize(gl.canvas)) {
// with no argument will resize to the canvas size
twgl.resizeFramebufferInfo(gl, fbi);
}
// calls gl.bindFramebuffer and gl.viewport
twgl.bindFramebufferInfo(gl, fbi);
// first draw the geometry to the texture
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.enable(gl.DEPTH_TEST);
var fov = Math.PI * .25;
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
var zNear = 0.1;
var zFar = 10;
var mat = m4.perspective(fov, aspect, zNear, zFar);
mat = m4.translate(mat, [0, 0, -3]);
mat = m4.rotateX(mat, time * 0.81);
mat = m4.rotateZ(mat, time * 0.77);
gl.useProgram(geoPrgInfo.program);
twgl.setBuffersAndAttributes(gl, geoPrgInfo, geoBufferInfo);
twgl.setUniforms(geoPrgInfo, {
matrix: mat,
u_lightDir: v3.normalize([1, 2, 3]),
});
gl.drawElements(gl.TRIANGLES, geoBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
// the texture now is black (0,0,0,0) where there's nothing and (1,1,1,1)
// where are geometry was drawn
// calls gl.bindFramebuffer and gl.viewport
twgl.bindFramebufferInfo(gl, null);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
// draw image using our texture as a mask
gl.useProgram(imgPrgInfo.program);
twgl.setBuffersAndAttributes(gl, imgPrgInfo, quadBufferInfo);
twgl.setUniforms(imgPrgInfo, {
color1Tex: textures.tex1,
color2Tex: textures.tex2,
maskTex: fbi.attachments[0],
});
gl.drawElements(gl.TRIANGLES, quadBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
<canvas></canvas>
In general you can probably achieve many more effects using the texture mask but it really depends on your goal.
Both approach can work.
The 'projection' one is probably more efficient and straightforward. It work with one single pass. You just need to substitute the classic UVs coordinates by the screen coordinates of vertices, in your vertex shader.
varying vec2 vTexCoord;
void main( void ){
// whatever how gl_Position is compute
gl_Position = uMVP * vec4(aPosition, 1.0);
// vTexCoord = aTexCoord;
// replace the standard UVs by the vertex screen position
vTexCoord = .5 * ( gl_Position.xy / gl_Position.w ) + .5;
}
You still need to tweak thoses texture coordinates to respect screen/texture aspect ratio, scale etc.

modify attribute vec2 variable

In my vertex shader I would love to modify attribute vec2 a_position variable that is shared in fragment shader. By this modifycation I should get image into cylindrical projection.
This is what I'm doing in my shaders:
<!-- vertex shader -->
<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;
uniform vec2 u_resolution;
uniform mat3 u_matrix;
varying vec2 v_texCoord;
void main() {
// modifying START
float angle = atan(a_position.y, a_position.x);
float r = sqrt(a_position.x*a_position.x + a_position.y*a_position.y);
a_position.x = r*cos(angle);
a_position.y = r*sin(angle);
// modifying STOP
gl_Position = vec4(u_matrix * vec3(a_position, 1), 1);
v_texCoord = a_position;
}
</script>
<!-- fragment shader -->
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
// our texture
uniform sampler2D u_image;
// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;
void main() {
gl_FragColor = texture2D(u_image, v_texCoord);
}
</script>
But I'm getting this error:
compiling shader '[object WebGLShader]':ERROR: 0:12: 'assign' : l-value required "a_position" (can't modify an attribute)
ERROR: 0:13: 'assign' : l-value required "a_position" (can't modify an attribute)
Don't you have any idea how to fix that?
Just use another variable
attribute vec2 a_position;
uniform vec2 u_resolution;
uniform mat3 u_matrix;
varying vec2 v_texCoord;
void main() {
// modifying START
float angle = atan(a_position.y, a_position.x);
float r = sqrt(a_position.x*a_position.x + a_position.y*a_position.y);
vec3 p = vec3(
r*cos(angle),
r*sin(angle),
1);
// modifying STOP
gl_Position = vec4(u_matrix * p, 1);
v_texCoord = a_position;
}

WebGL: enablevertexattribarray index out of range

Here my vertex and fragment shaders:
<script id="shader-fs" type="x-shader/x-fragment">
precision mediump float;
uniform sampler2D uSampler;
varying vec4 vColor;
varying vec2 vTextureCoord;
void main(void) {
gl_FragColor = vColor;
// gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
}
</script>
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 aVertexPosition;
attribute vec4 aVertexColor;
attribute vec2 aTextureCoord;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
varying vec4 vColor;
varying vec2 vTextureCoord;
void main(void) {
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
vColor = aVertexColor;
// vTextureCoord = aTextureCoord;
}
</script>
And here's my shader initializer:
function initShaders() {
var fragmentShader = getShader(gl, "shader-fs");
var vertexShader = getShader(gl, "shader-vs");
shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert("Could not initialise shaders");
}
gl.useProgram(shaderProgram);
shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
shaderProgram.vertexColorAttribute = gl.getAttribLocation(shaderProgram, "aVertexColor");
gl.enableVertexAttribArray(shaderProgram.vertexColorAttribute);
shaderProgram.textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureCoord");
gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute);
shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler");
}
The error comes from this line:
gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute);
>> enablevertexattribarray index out of range
How do I deal with it ?
Thats simply because you do not use aTextureCoord in your vertex program, so the GLSL-Compiler optimizes it by removing it. You really should check the result of gl.GetAttribLocation() for errors, and enable only the attributes that are present in your program. Issuing a warning in case an attribute is missing would be sufficient, I know no way to distinguish shader-authoring-errors from optimizations by the compiler.