How to get a "Glow" shader effect in OpenGL ES 2.0? - ios

I'm writing a 3D app for iOS. I'm new to OpenGL ES 2.0, so I'm still getting myself around writing basic shaders.
I really need to implement a "Glow" effect on some of my models, based on the texturing.
Here's a sample:
.
I'm looking for code examples for OpenGL ES 2.0. Most code I find on the internet is either for desktop OpenGL or D3D.
Any ideas?

First of all there are tons of algorithms and techniques to generate a glow effect.
I just want to present one possibility.
Create a Material that is luminescent.
For this I use a modified Blinn-Phong light model, where the direction to the light source is always the inverse direction of the normal vector of the fragment.
varying vec3 vertPos;
varying vec3 vertNV;
varying vec3 vertCol;
uniform float u_glow;
void main()
{
vec3 color = vertCol;
float shininess = 10.0;
vec3 normalV = normalize( vertNV );
vec3 eyeV = normalize( -vertPos );
vec3 halfV = normalize( eyeV + normalV );
float NdotH = max( 0.0, dot( normalV, halfV ) );
float glowFac = ( shininess + 2.0 ) * pow( NdotH, shininess ) / ( 2.0 * 3.14159265 );
gl_FragColor = vec4( u_glow * (0.1 + color.rgb * glowFac * 0.5), 1.0 );
}
In a second step a gaussian blur algorithm is performed on the output. The scene is written to frame buffer with a texture bound to the color plane. A screen space pass uses the texture as the input to blur the output.
For performance reasons, the blur algorithm is first performed along the X-axis of the viewport and in a further step along the Y-axis of the viewport.
varying vec2 vertPos;
uniform sampler2D u_textureCol;
uniform vec2 u_textureSize;
uniform float u_sigma;
uniform int u_width;
float CalcGauss( float x, float sigma )
{
float coeff = 1.0 / (2.0 * 3.14157 * sigma);
float expon = -(x*x) / (2.0 * sigma);
return (coeff*exp(expon));
}
void main()
{
vec2 texC = vertPos.st * 0.5 + 0.5;
vec4 texCol = texture( u_textureCol, texC );
vec4 gaussCol = vec4( texCol.rgb, 1.0 );
vec2 step = 1.0 / u_textureSize;
for ( int i = 1; i <= u_width; ++ i )
{
vec2 actStep = vec2( float(i) * step.x, 0.0 ); // this is for the X-axis
// vec2 actStep = vec2( 0.0, float(i) * step.y ); this would be for the Y-axis
float weight = CalcGauss( float(i) / float(u_width), u_sigma );
texCol = texture2D( u_textureCol, texC + actStep );
gaussCol += vec4( texCol.rgb * weight, weight );
texCol = texture2D( u_textureCol, texC - actStep );
gaussCol += vec4( texCol.rgb * weight, weight );
}
gaussCol.rgb /= gaussCol.w;
gl_FragColor = vec4( gaussCol.rgb, 1.0 );
}
For the implementation of a blur algorithm see also the answer to the questions:
OpenGL es 2.0 Gaussian blur on triangle
What kind of blurs can be implemented in pixel shaders?
See the following similar WebGL example which puts all together:
var readInput = true;
function changeEventHandler(event){
readInput = true;
}
(function loadscene() {
var resize, gl, progDraw, progBlurX, progPost, vp_size, blurFB;
var bufCube = {};
var bufQuad = {};
var shininess = 10.0;
var glow = 10.0;
var sigma = 0.8;
function render(delteMS){
//if ( readInput ) {
readInput = false;
var sliderScale = 100;
shininess = document.getElementById( "shine" ).value;
glow = document.getElementById( "glow" ).value / sliderScale;
sigma = document.getElementById( "sigma" ).value / sliderScale;
//}
Camera.create();
Camera.vp = vp_size;
gl.enable( gl.DEPTH_TEST );
gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
// set up framebuffer
gl.bindFramebuffer( gl.FRAMEBUFFER, blurFB[0] );
gl.viewport( 0, 0, blurFB[0].width, blurFB[0].height );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
// set up draw shader
ShaderProgram.Use( progDraw.prog );
ShaderProgram.SetUniformM44( progDraw.prog, "u_projectionMat44", Camera.Perspective() );
ShaderProgram.SetUniformM44( progDraw.prog, "u_viewMat44", Camera.LookAt() );
var modelMat = IdentityMat44()
modelMat = RotateAxis( modelMat, CalcAng( delteMS, 13.0 ), 0 );
modelMat = RotateAxis( modelMat, CalcAng( delteMS, 17.0 ), 1 );
ShaderProgram.SetUniformM44( progDraw.prog, "u_modelMat44", modelMat );
ShaderProgram.SetUniformF1( progDraw.prog, "u_shininess", shininess );
ShaderProgram.SetUniformF1( progDraw.prog, "u_glow", glow );
// draw scene
VertexBuffer.Draw( bufCube );
// set blur-X framebuffer and bind frambuffer texture
gl.bindFramebuffer( gl.FRAMEBUFFER, blurFB[1] );
gl.viewport( 0, 0, blurFB[1].width, blurFB[1].height );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
var texUnit = 1;
gl.activeTexture( gl.TEXTURE0 + texUnit );
gl.bindTexture( gl.TEXTURE_2D, blurFB[0].color0_texture );
// set up blur-X shader
ShaderProgram.Use( progBlurX.prog );
ShaderProgram.SetUniformI1( progBlurX.prog , "u_texture", texUnit )
ShaderProgram.SetUniformF2( progBlurX.prog , "u_textureSize", vp_size );
ShaderProgram.SetUniformF1( progBlurX.prog , "u_sigma", sigma )
// draw full screen space
gl.enableVertexAttribArray( progBlurX.inPos );
gl.bindBuffer( gl.ARRAY_BUFFER, bufQuad.pos );
gl.vertexAttribPointer( progBlurX.inPos, 2, gl.FLOAT, false, 0, 0 );
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufQuad.inx );
gl.drawElements( gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 );
gl.disableVertexAttribArray( progBlurX.inPos );
// reset framebuffer and bind frambuffer texture
gl.bindFramebuffer( gl.FRAMEBUFFER, null );
gl.viewport( 0, 0, vp_size[0], vp_size[1] );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
texUnit = 2;
gl.activeTexture( gl.TEXTURE0 + texUnit );
gl.bindTexture( gl.TEXTURE_2D, blurFB[1].color0_texture );
// set up pst process shader
ShaderProgram.Use( progPost.prog );
ShaderProgram.SetUniformI1( progPost.prog, "u_texture", texUnit )
ShaderProgram.SetUniformF2( progPost.prog, "u_textureSize", vp_size );
ShaderProgram.SetUniformF1( progPost.prog, "u_sigma", sigma );
// draw full screen space
gl.enableVertexAttribArray( progPost.inPos );
gl.bindBuffer( gl.ARRAY_BUFFER, bufQuad.pos );
gl.vertexAttribPointer( progPost.inPos, 2, gl.FLOAT, false, 0, 0 );
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufQuad.inx );
gl.drawElements( gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 );
gl.disableVertexAttribArray( progPost.inPos );
requestAnimationFrame(render);
}
function resize() {
//vp_size = [gl.drawingBufferWidth, gl.drawingBufferHeight];
vp_size = [window.innerWidth, window.innerHeight]
canvas.width = vp_size[0];
canvas.height = vp_size[1];
var fbsize = Math.max(vp_size[0], vp_size[1])-1;
fbsize = 1 << 31 - Math.clz32(fbsize); // nearest power of 2
fbsize = fbsize * 2
blurFB = [];
for ( var i = 0; i < 2; ++ i ) {
fb = gl.createFramebuffer();
fb.width = fbsize;
fb.height = fbsize;
gl.bindFramebuffer( gl.FRAMEBUFFER, fb );
fb.color0_texture = gl.createTexture();
gl.bindTexture( gl.TEXTURE_2D, fb.color0_texture );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST );
gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, fb.width, fb.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null );
fb.renderbuffer = gl.createRenderbuffer();
gl.bindRenderbuffer( gl.RENDERBUFFER, fb.renderbuffer );
gl.renderbufferStorage( gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, fb.width, fb.height );
gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, fb.color0_texture, 0 );
gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, fb.renderbuffer );
gl.bindTexture( gl.TEXTURE_2D, null );
gl.bindRenderbuffer( gl.RENDERBUFFER, null );
gl.bindFramebuffer( gl.FRAMEBUFFER, null );
blurFB.push( fb );
}
}
function initScene() {
canvas = document.getElementById( "canvas");
gl = canvas.getContext( "experimental-webgl" );
if ( !gl )
return null;
progDraw = {}
progDraw.prog = ShaderProgram.Create(
[ { source : "draw-shader-vs", stage : gl.VERTEX_SHADER },
{ source : "draw-shader-fs", stage : gl.FRAGMENT_SHADER }
] );
if ( !progDraw.prog )
return null;
progDraw.inPos = gl.getAttribLocation( progDraw.prog, "inPos" );
progDraw.inNV = gl.getAttribLocation( progDraw.prog, "inNV" );
progDraw.inCol = gl.getAttribLocation( progDraw.prog, "inCol" );
progBlurX = {}
progBlurX.prog = ShaderProgram.Create(
[ { source : "post-shader-vs", stage : gl.VERTEX_SHADER },
{ source : "blurX-shader-fs", stage : gl.FRAGMENT_SHADER }
] );
progBlurX.inPos = gl.getAttribLocation( progBlurX.prog, "inPos" );
if ( !progBlurX.prog )
return;
progPost = {}
progPost.prog = ShaderProgram.Create(
[ { source : "post-shader-vs", stage : gl.VERTEX_SHADER },
{ source : "blurY-shader-fs", stage : gl.FRAGMENT_SHADER }
] );
progPost.inPos = gl.getAttribLocation( progPost.prog, "inPos" );
if ( !progPost.prog )
return;
// create cube
var cubePos = [
-1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0,
-1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0 ];
var cubeCol = [ 1.0, 0.0, 0.0, 1.0, 0.5, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 ];
var cubeHlpInx = [ 0, 1, 2, 3, 1, 5, 6, 2, 5, 4, 7, 6, 4, 0, 3, 7, 3, 2, 6, 7, 1, 0, 4, 5 ];
var cubePosData = [];
for ( var i = 0; i < cubeHlpInx.length; ++ i ) {
cubePosData.push( cubePos[cubeHlpInx[i]*3], cubePos[cubeHlpInx[i]*3+1], cubePos[cubeHlpInx[i]*3+2] );
}
var cubeNVData = [];
for ( var i1 = 0; i1 < cubeHlpInx.length; i1 += 4 ) {
var nv = [0, 0, 0];
for ( i2 = 0; i2 < 4; ++ i2 ) {
var i = i1 + i2;
nv[0] += cubePosData[i*3]; nv[1] += cubePosData[i*3+1]; nv[2] += cubePosData[i*3+2];
}
for ( i2 = 0; i2 < 4; ++ i2 )
cubeNVData.push( nv[0], nv[1], nv[2] );
}
var cubeColData = [];
for ( var is = 0; is < 6; ++ is ) {
for ( var ip = 0; ip < 4; ++ ip ) {
cubeColData.push( cubeCol[is*3], cubeCol[is*3+1], cubeCol[is*3+2] );
}
}
var cubeInxData = [];
for ( var i = 0; i < cubeHlpInx.length; i += 4 ) {
cubeInxData.push( i, i+1, i+2, i, i+2, i+3 );
}
bufCube = VertexBuffer.Create(
[ { data : cubePosData, attrSize : 3, attrLoc : progDraw.inPos },
{ data : cubeNVData, attrSize : 3, attrLoc : progDraw.inNV },
{ data : cubeColData, attrSize : 3, attrLoc : progDraw.inCol } ],
cubeInxData );
bufQuad.pos = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, bufQuad.pos );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( [ -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0 ] ), gl.STATIC_DRAW );
bufQuad.inx = gl.createBuffer();
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufQuad.inx );
gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( [ 0, 1, 2, 0, 2, 3 ] ), gl.STATIC_DRAW );
window.onresize = resize;
resize();
requestAnimationFrame(render);
}
function Fract( val ) {
return val - Math.trunc( val );
}
function CalcAng( deltaTime, intervall ) {
return Fract( deltaTime / (1000*intervall) ) * 2.0 * Math.PI;
}
function CalcMove( deltaTime, intervall, range ) {
var pos = self.Fract( deltaTime / (1000*intervall) ) * 2.0
var pos = pos < 1.0 ? pos : (2.0-pos)
return range[0] + (range[1] - range[0]) * pos;
}
function EllipticalPosition( a, b, angRag ) {
var a_b = a * a - b * b
var ea = (a_b <= 0) ? 0 : Math.sqrt( a_b );
var eb = (a_b >= 0) ? 0 : Math.sqrt( -a_b );
return [ a * Math.sin( angRag ) - ea, b * Math.cos( angRag ) - eb, 0 ];
}
glArrayType = typeof Float32Array !="undefined" ? Float32Array : ( typeof WebGLFloatArray != "undefined" ? WebGLFloatArray : Array );
function IdentityMat44() {
var m = new glArrayType(16);
m[0] = 1; m[1] = 0; m[2] = 0; m[3] = 0;
m[4] = 0; m[5] = 1; m[6] = 0; m[7] = 0;
m[8] = 0; m[9] = 0; m[10] = 1; m[11] = 0;
m[12] = 0; m[13] = 0; m[14] = 0; m[15] = 1;
return m;
};
function RotateAxis(matA, angRad, axis) {
var aMap = [ [1, 2], [2, 0], [0, 1] ];
var a0 = aMap[axis][0], a1 = aMap[axis][1];
var sinAng = Math.sin(angRad), cosAng = Math.cos(angRad);
var matB = new glArrayType(16);
for ( var i = 0; i < 16; ++ i ) matB[i] = matA[i];
for ( var i = 0; i < 3; ++ i ) {
matB[a0*4+i] = matA[a0*4+i] * cosAng + matA[a1*4+i] * sinAng;
matB[a1*4+i] = matA[a0*4+i] * -sinAng + matA[a1*4+i] * cosAng;
}
return matB;
}
function Cross( a, b ) { return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0], 0.0 ]; }
function Dot( a, b ) { return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]; }
function Normalize( v ) {
var len = Math.sqrt( v[0] * v[0] + v[1] * v[1] + v[2] * v[2] );
return [ v[0] / len, v[1] / len, v[2] / len ];
}
var Camera = {};
Camera.create = function() {
this.pos = [0, 3, 0.0];
this.target = [0, 0, 0];
this.up = [0, 0, 1];
this.fov_y = 90;
this.vp = [800, 600];
this.near = 0.5;
this.far = 100.0;
}
Camera.Perspective = function() {
var fn = this.far + this.near;
var f_n = this.far - this.near;
var r = this.vp[0] / this.vp[1];
var t = 1 / Math.tan( Math.PI * this.fov_y / 360 );
var m = IdentityMat44();
m[0] = t/r; m[1] = 0; m[2] = 0; m[3] = 0;
m[4] = 0; m[5] = t; m[6] = 0; m[7] = 0;
m[8] = 0; m[9] = 0; m[10] = -fn / f_n; m[11] = -1;
m[12] = 0; m[13] = 0; m[14] = -2 * this.far * this.near / f_n; m[15] = 0;
return m;
}
Camera.LookAt = function() {
var mz = Normalize( [ this.pos[0]-this.target[0], this.pos[1]-this.target[1], this.pos[2]-this.target[2] ] );
var mx = Normalize( Cross( this.up, mz ) );
var my = Normalize( Cross( mz, mx ) );
var tx = Dot( mx, this.pos );
var ty = Dot( my, this.pos );
var tz = Dot( [-mz[0], -mz[1], -mz[2]], this.pos );
var m = IdentityMat44();
m[0] = mx[0]; m[1] = my[0]; m[2] = mz[0]; m[3] = 0;
m[4] = mx[1]; m[5] = my[1]; m[6] = mz[1]; m[7] = 0;
m[8] = mx[2]; m[9] = my[2]; m[10] = mz[2]; m[11] = 0;
m[12] = tx; m[13] = ty; m[14] = tz; m[15] = 1;
return m;
}
var ShaderProgram = {};
ShaderProgram.Create = function( shaderList ) {
var shaderObjs = [];
for ( var i_sh = 0; i_sh < shaderList.length; ++ i_sh ) {
var shderObj = this.CompileShader( shaderList[i_sh].source, shaderList[i_sh].stage );
if ( shderObj == 0 )
return 0;
shaderObjs.push( shderObj );
}
var progObj = this.LinkProgram( shaderObjs )
if ( progObj != 0 ) {
progObj.attribIndex = {};
var noOfAttributes = gl.getProgramParameter( progObj, gl.ACTIVE_ATTRIBUTES );
for ( var i_n = 0; i_n < noOfAttributes; ++ i_n ) {
var name = gl.getActiveAttrib( progObj, i_n ).name;
progObj.attribIndex[name] = gl.getAttribLocation( progObj, name );
}
progObj.unifomLocation = {};
var noOfUniforms = gl.getProgramParameter( progObj, gl.ACTIVE_UNIFORMS );
for ( var i_n = 0; i_n < noOfUniforms; ++ i_n ) {
var name = gl.getActiveUniform( progObj, i_n ).name;
progObj.unifomLocation[name] = gl.getUniformLocation( progObj, name );
}
}
return progObj;
}
ShaderProgram.AttributeIndex = function( progObj, name ) { return progObj.attribIndex[name]; }
ShaderProgram.UniformLocation = function( progObj, name ) { return progObj.unifomLocation[name]; }
ShaderProgram.Use = function( progObj ) { gl.useProgram( progObj ); }
ShaderProgram.SetUniformI1 = function( progObj, name, val ) { if(progObj.unifomLocation[name]) gl.uniform1i( progObj.unifomLocation[name], val ); }
ShaderProgram.SetUniformF1 = function( progObj, name, val ) { if(progObj.unifomLocation[name]) gl.uniform1f( progObj.unifomLocation[name], val ); }
ShaderProgram.SetUniformF2 = function( progObj, name, arr ) { if(progObj.unifomLocation[name]) gl.uniform2fv( progObj.unifomLocation[name], arr ); }
ShaderProgram.SetUniformF3 = function( progObj, name, arr ) { if(progObj.unifomLocation[name]) gl.uniform3fv( progObj.unifomLocation[name], arr ); }
ShaderProgram.SetUniformF4 = function( progObj, name, arr ) { if(progObj.unifomLocation[name]) gl.uniform4fv( progObj.unifomLocation[name], arr ); }
ShaderProgram.SetUniformM33 = function( progObj, name, mat ) { if(progObj.unifomLocation[name]) gl.uniformMatrix3fv( progObj.unifomLocation[name], false, mat ); }
ShaderProgram.SetUniformM44 = function( progObj, name, mat ) { if(progObj.unifomLocation[name]) gl.uniformMatrix4fv( progObj.unifomLocation[name], false, mat ); }
ShaderProgram.CompileShader = function( source, shaderStage ) {
var shaderScript = document.getElementById(source);
if (shaderScript)
source = shaderScript.text;
var shaderObj = gl.createShader( shaderStage );
gl.shaderSource( shaderObj, source );
gl.compileShader( shaderObj );
var status = gl.getShaderParameter( shaderObj, gl.COMPILE_STATUS );
if ( !status ) alert(gl.getShaderInfoLog(shaderObj));
return status ? shaderObj : null;
}
ShaderProgram.LinkProgram = function( shaderObjs ) {
var prog = gl.createProgram();
for ( var i_sh = 0; i_sh < shaderObjs.length; ++ i_sh )
gl.attachShader( prog, shaderObjs[i_sh] );
gl.linkProgram( prog );
status = gl.getProgramParameter( prog, gl.LINK_STATUS );
if ( !status ) alert("Could not initialise shaders");
gl.useProgram( null );
return status ? prog : null;
}
var VertexBuffer = {};
VertexBuffer.Create = function( attributes, indices ) {
var buffer = {};
buffer.buf = [];
buffer.attr = []
for ( var i = 0; i < attributes.length; ++ i ) {
buffer.buf.push( gl.createBuffer() );
buffer.attr.push( { size : attributes[i].attrSize, loc : attributes[i].attrLoc } );
gl.bindBuffer( gl.ARRAY_BUFFER, buffer.buf[i] );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( attributes[i].data ), gl.STATIC_DRAW );
}
buffer.inx = gl.createBuffer();
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, buffer.inx );
gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( indices ), gl.STATIC_DRAW );
buffer.inxLen = indices.length;
gl.bindBuffer( gl.ARRAY_BUFFER, null );
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null );
return buffer;
}
VertexBuffer.Draw = function( bufObj ) {
for ( var i = 0; i < bufObj.buf.length; ++ i ) {
gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.buf[i] );
gl.vertexAttribPointer( bufObj.attr[i].loc, bufObj.attr[i].size, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( bufObj.attr[i].loc );
}
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufObj.inx );
gl.drawElements( gl.TRIANGLES, bufObj.inxLen, gl.UNSIGNED_SHORT, 0 );
for ( var i = 0; i < bufObj.buf.length; ++ i )
gl.disableVertexAttribArray( bufObj.attr[i].loc );
gl.bindBuffer( gl.ARRAY_BUFFER, null );
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null );
}
initScene();
})();
html,body {
height: 100%;
width: 100%;
margin: 0;
overflow: hidden;
}
#gui {
position : absolute;
top : 0;
left : 0;
}
<script id="draw-shader-vs" type="x-shader/x-vertex">
precision highp float;
attribute vec3 inPos;
attribute vec3 inNV;
attribute vec3 inCol;
varying vec3 vertPos;
varying vec3 vertNV;
varying vec3 vertCol;
uniform mat4 u_projectionMat44;
uniform mat4 u_viewMat44;
uniform mat4 u_modelMat44;
void main()
{
mat4 mv = u_viewMat44 * u_modelMat44;
vertCol = inCol;
vertNV = normalize(mat3(mv) * inNV);
vec4 viewPos = mv * vec4( inPos, 1.0 );
vertPos = viewPos.xyz;
gl_Position = u_projectionMat44 * viewPos;
}
</script>
<script id="draw-shader-fs" type="x-shader/x-fragment">
precision mediump float;
varying vec3 vertPos;
varying vec3 vertNV;
varying vec3 vertCol;
uniform float u_shininess;
uniform float u_glow;
void main()
{
vec3 color = vertCol;
vec3 normalV = normalize( vertNV );
vec3 eyeV = normalize( -vertPos );
vec3 halfV = normalize( eyeV + normalV );
float NdotH = max( 0.0, dot( normalV, halfV ) );
float shineFac = ( u_shininess + 2.0 ) * pow( NdotH, u_shininess ) / ( 2.0 * 3.14159265 );
gl_FragColor = vec4( u_glow*0.1 + color.rgb * u_glow * shineFac * 0.5, 1.0 );
}
</script>
<script id="post-shader-vs" type="x-shader/x-vertex">
precision mediump float;
attribute vec2 inPos;
varying vec2 pos;
void main()
{
pos = inPos;
gl_Position = vec4( inPos, 0.0, 1.0 );
}
</script>
<script id="blurX-shader-fs" type="x-shader/x-fragment">
precision mediump float;
varying vec2 pos;
uniform sampler2D u_texture;
uniform vec2 u_textureSize;
uniform float u_sigma;
float CalcGauss( float x, float sigma )
{
float coeff = 1.0 / (2.0 * 3.14157 * sigma);
float expon = -(x*x) / (2.0 * sigma);
return (coeff*exp(expon));
}
void main()
{
vec2 texC = pos.st * 0.5 + 0.5;
vec4 texCol = texture2D( u_texture, texC );
vec4 gaussCol = vec4( texCol.rgb, 1.0 );
float stepX = 1.0 / u_textureSize.x;
for ( int i = 1; i <= 20; ++ i )
{
float weight = CalcGauss( float(i) / 32.0, u_sigma * 0.5 );
texCol = texture2D( u_texture, texC + vec2( float(i) * stepX, 0.0 ) );
gaussCol += vec4( texCol.rgb * weight, weight );
texCol = texture2D( u_texture, texC - vec2( float(i) * stepX, 0.0 ) );
gaussCol += vec4( texCol.rgb * weight, weight );
}
gaussCol.rgb /= gaussCol.w;
gl_FragColor = vec4( gaussCol.rgb, 1.0 );
}
</script>
<script id="blurY-shader-fs" type="x-shader/x-fragment">
precision mediump float;
varying vec2 pos;
uniform sampler2D u_texture;
uniform vec2 u_textureSize;
uniform float u_sigma;
float CalcGauss( float x, float sigma )
{
float coeff = 1.0 / (2.0 * 3.14157 * sigma);
float expon = -(x*x) / (2.0 * sigma);
return (coeff*exp(expon));
}
void main()
{
vec2 texC = pos.st * 0.5 + 0.5;
vec4 texCol = texture2D( u_texture, texC );
vec4 gaussCol = vec4( texCol.rgb, 1.0 );
float stepY = 1.0 / u_textureSize.y;
for ( int i = 1; i <= 20; ++ i )
{
float weight = CalcGauss( float(i) / 32.0, u_sigma * 0.5 );
texCol = texture2D( u_texture, texC + vec2( 0.0, float(i) * stepY ) );
gaussCol += vec4( texCol.rgb * weight, weight );
texCol = texture2D( u_texture, texC - vec2( 0.0, float(i) * stepY ) );
gaussCol += vec4( texCol.rgb * weight, weight );
}
vec3 hdrCol = 2.0 * gaussCol.xyz / gaussCol.w;
vec3 mappedCol = vec3( 1.0 ) - exp( -hdrCol.rgb * 3.0 );
gl_FragColor = vec4( clamp( mappedCol.rgb, 0.0, 1.0 ), 1.0 );
}
</script>
<div>
<form id="gui" name="inputs">
<table>
<tr> <td> <font color= #CCF>shininess</font> </td>
<td> <input type="range" id="shine" min="0" max="50" value="10" onchange="changeEventHandler(event);"/></td> </tr>
<tr> <td> <font color= #CCF>glow</font> </td>
<td> <input type="range" id="glow" min="100" max="400" value="250" onchange="changeEventHandler(event);"/></td> </tr>
<tr> <td> <font color= #CCF>blur</font> </td>
<td> <input type="range" id="sigma" min="1" max="100" value="60" onchange="changeEventHandler(event);"/></td> </tr>
</table>
</form>
</div>
<canvas id="canvas" style="border: none;" width="100%" height="100%"></canvas>

The website GLSL Sandbox has a collection of shader examples. This one has the glow and appears to be able to compile for ES.
You should be able to modify these to pull uv's from your texture.
Here is some code directly from this site:
#ifdef GL_ES
precision mediump float;
#endif
#extension GL_OES_standard_derivatives : enable
uniform float time;
uniform vec2 mouse;
uniform vec2 resolution;
void main(void){
vec2 p = (gl_FragCoord.xy * 2.0 - resolution) / min(resolution.x, resolution.y);
vec3 color1 = vec3(0.0, 0.3, 0.5);
vec3 color2 = vec3(0.5, 0.0, 0.3);
float f = 0.0;
float g = 0.0;
float h = 0.0;
float PI = 3.14159265;
for(float i = 0.0; i < 40.0; i++){
if (floor(mouse.x * 41.0) < i)
break;
float s = sin(time + i * PI / 20.0) * 0.8;
float c = cos(time + i * PI / 20.0) * 0.8;
float d = abs(p.x + c);
float e = abs(p.y + s);
f += 0.001 / d;
g += 0.001 / e;
h += 0.00003 / (d * e);
}
gl_FragColor = vec4(f * color1 + g * color2 + vec3(h), 1.0);
}

Related

Problems by coloring WebGL 3D Object

I'm trying to implement a WebGL app according to the documentation in the Mozilla Docs.
My code generates a sphere with is shaped by a scalefactor. The colors are generated according to the scalefactor. The shape is ok, but the colors are wrong. So what is going wrong - I have no clue. This code works on Android and in Java. I'm using the latest Chrome browser.
Here is the code:
export function createHcm3dObject(gl, diagram3D, deltaTheta, deltaPhi) {
let positions = [];
let colors = [];
let alpha = 1.0;
for (let theta = 0; theta < 360; theta += deltaTheta) {
for (let phi = 0; phi < 180; phi += deltaPhi) {
//r is scalefactor between 0 and 1 which shapes the sphere
let r = diagram3D[theta][phi];
//Color is generated according to the radius (alpha is currently set to 1.0)
let x1Color = generateColorArray(r, alpha);
let x1 = r * Math.sin(math3d.toRadians(phi)) * Math.cos(math3d.toRadians(theta));
let y1 = r * Math.sin(math3d.toRadians(phi)) * Math.sin(math3d.toRadians(theta));
let z1 = r * Math.cos(math3d.toRadians(phi));
r = diagram3D[theta + deltaTheta][phi];
let x2Color = generateColorArray(r, alpha);
let x2 = r * Math.sin(math3d.toRadians(phi)) * Math.cos(math3d.toRadians(theta + deltaTheta));
let y2 = r * Math.sin(math3d.toRadians(phi)) * Math.sin(math3d.toRadians(theta + deltaTheta));
let z2 = r * Math.cos(math3d.toRadians(phi));
r = diagram3D[theta][phi + deltaPhi];
let x3Color = generateColorArray(r, alpha);
let x3 = r * Math.sin(math3d.toRadians(phi + deltaPhi)) * Math.cos(math3d.toRadians(theta));
let y3 = r * Math.sin(math3d.toRadians(phi + deltaPhi)) * Math.sin(math3d.toRadians(theta));
let z3 = r * Math.cos(math3d.toRadians(phi + deltaPhi));
r = diagram3D[theta + deltaTheta][phi + deltaPhi];
let x4Color = generateColorArray(r, alpha);
let x4 = r * Math.sin(math3d.toRadians(phi + deltaPhi)) * Math.cos(math3d.toRadians(theta + deltaTheta));
let y4 = r * Math.sin(math3d.toRadians(phi + deltaPhi)) * Math.sin(math3d.toRadians(theta + deltaTheta));
let z4 = r * Math.cos(math3d.toRadians(phi + deltaPhi));
//1. Triangle
positions.push(x1, y1, z1);
positions.push(x3, y3, z3);
positions.push(x4, y4, z4);
//2. Triangle
positions.push(x2, y2, z2);
positions.push(x1, y1, z1);
positions.push(x4, y4, z4);
//Colors for 1. Triangle (red,green,blue,alpha=1.0)
colors.push(x1Color[0], x1Color[1], x1Color[2], x1Color[3]);
colors.push(x3Color[0], x3Color[1], x3Color[2], x3Color[3]);
colors.push(x4Color[0], x4Color[1], x4Color[2], x4Color[3]);
//Colors for 2. Triangle
colors.push(x2Color[0], x2Color[1], x2Color[2], x2Color[3]);
colors.push(x1Color[0], x1Color[1], x1Color[2], x1Color[3]);
colors.push(x4Color[0], x4Color[1], x4Color[2], x4Color[3]);
}
//console.log(positions);
//console.log(colors);
}
// Now pass the list of positions into WebGL to build the
// shape. We do this by creating a Float32Array from the
// JavaScript array, then use it to fill the current buffer.
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
return {
position: positionBuffer,
color: colorBuffer,
positionSize: positions.length,
deltaTheta,
deltaPhi
};
};
function generateColorArray(r, alpha) {
let colorQuad = [];
let green = Math.abs(Math.sin(2 * r * Math.PI));
let blue = Math.abs(Math.cos(2 * r * Math.PI));
colorQuad[0] = 0.0;
colorQuad[1] = green;
colorQuad[2] = blue;
colorQuad[3] = alpha;
if (r >= 0.5 / 2) {
let red = Math.abs(Math.cos(2 * r * Math.PI));
green = Math.abs(Math.sin(2 * r * Math.PI));
if (r < 0.5) {
green = 1.0;
}
colorQuad[0] = red;
colorQuad[1] = green;
colorQuad[2] = 0.0;
colorQuad[3] = alpha;
}
if (r >= 0.5) {
let red = Math.abs(Math.cos(2 * r * Math.PI));
green = Math.abs(Math.cos(2 * r * Math.PI));
if (r < 0.75) {
red = 1.0;
}
colorQuad[0] = red;
colorQuad[1] = green;
colorQuad[2] = 0.0;
colorQuad[3] = alpha;
}
if (r >= 0.75) {
let red = 1.0;
blue = Math.abs(Math.cos(2 * r * Math.PI));
colorQuad[0] = red;
colorQuad[1] = 0.0;
colorQuad[2] = blue;
colorQuad[3] = alpha;
}
return colorQuad;
}
React Class:
export class Viewer3d extends Component {
state = {
rotX: 0,
rotY: 0,
gl: null,
buffers: null,
programInfo: null,
};
componentDidMount() {
this.init();
}
init = () => {
console.log("Comp did mount");
const canvas = document.querySelector("#glCanvas");
/** #type {WebGLRenderingContext} */
const gl = canvas.getContext("webgl");
if (!gl) {
alert(
"Unable to initialize WebGL. Your browser or machine may not support it."
);
return;
}
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
let vs = document.getElementById("vshader").textContent;
let fs = document.getElementById("fshader").textContent;
//console.log(vs+" "+fs);
const shaderProgram = shader.initShaderProgram(gl, vs, fs);
let diagram3D = [];
let deltaTheta = 10;
let deltaPhi = 10;
for (let theta = 0; theta <= 360; theta += deltaTheta) {
let phiArray = [];
for (let phi = 0; phi <= 180; phi += deltaPhi) {
let eleCorr = 90 - phi;
let thetaCorr = 360 - theta;
let out = engine.antenna_correction(
thetaCorr,
0,
eleCorr,
0,
"012EA34",
"012EA34"
);
let att = out.a;
let logarithmic = false;
if (logarithmic) {
att = 1.0 - (-20.0 * Math.log10(att)) / 40.0;
}
phiArray[phi] = att;
}
diagram3D[theta] = phiArray;
}
//console.log(diagram3D);
const buffers = hcm3d.createHcm3dObject(
gl,
diagram3D,
deltaTheta,
deltaPhi
);
const programInfo = {
program: shaderProgram,
attribLocations: {
vertexPosition: gl.getAttribLocation(shaderProgram, "aVertexPosition"),
vertexColor: gl.getAttribLocation(shaderProgram,"aVertexColor"),
},
uniformLocations: {
projectionMatrix: gl.getUniformLocation(shaderProgram,"uProjectionMatrix"),
modelViewMatrix: gl.getUniformLocation(shaderProgram,"uModelViewMatrix"),
},
};
this.setState({ gl, buffers, programInfo });
this.drawScene(gl, programInfo, buffers);
};
drawScene = (gl, programInfo, buffers) => {
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
// Clear the canvas before we start drawing on it.
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// Create a perspective matrix, a special matrix that is
// used to simulate the distortion of perspective in a camera.
// Our field of view is 45 degrees, with a width/height
// ratio that matches the display size of the canvas
// and we only want to see objects between 0.1 units
// and 100 units away from the camera.
const fieldOfView = (45 * Math.PI) / 180; // in radians
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.1;
const zFar = 100.0;
const projectionMatrix = mat4.create();
// note: glmatrix.js always has the first argument
// as the destination to receive the result.
mat4.perspective(projectionMatrix, fieldOfView, aspect, zNear, zFar);
// Set the drawing position to the "identity" point, which is
// the center of the scene.
const modelViewMatrix = mat4.create();
// Now move the drawing position a bit to where we want to
// start drawing the square.
mat4.translate(
modelViewMatrix, // destination matrix
modelViewMatrix, // matrix to translate
[0, 0, -2.5]
);
mat4.rotate(modelViewMatrix, modelViewMatrix, this.state.rotY, [1, 0, 0]);
mat4.rotate(modelViewMatrix, modelViewMatrix, this.state.rotX, [0, 1, 0]);
gl.bindBuffer(gl.ARRAY_BUFFER, buffers.color);
gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position);
gl.vertexAttribPointer(
programInfo.attribLocations.vertexPosition,
3,
gl.FLOAT,
false,
0,
0
);
gl.vertexAttribPointer(
programInfo.attribLocations.vertexColor,
4,
gl.FLOAT,
false,
0,
0
);
gl.enableVertexAttribArray(programInfo.attribLocations.vertexPosition);
gl.enableVertexAttribArray(programInfo.attribLocations.vertexColor);
gl.useProgram(programInfo.program);
gl.uniformMatrix4fv(
programInfo.uniformLocations.projectionMatrix,
false,
projectionMatrix
);
gl.uniformMatrix4fv(
programInfo.uniformLocations.modelViewMatrix,
false,
modelViewMatrix
);
gl.drawArrays(gl.TRIANGLES, 0, buffers.positionSize);
};
onMouseMove = (evt) => {
if (!mouseDown) {
return;
}
evt.preventDefault();
let deltaX = evt.clientX - mouseX;
let deltaY = evt.clientY - mouseY;
mouseX = evt.clientX;
mouseY = evt.clientY;
this.rotateScene(deltaX, deltaY);
};
onMouseDown = (evt) => {
evt.preventDefault();
mouseDown = true;
mouseX = evt.clientX;
mouseY = evt.clientY;
};
onMouseUp = (evt) => {
evt.preventDefault();
mouseDown = false;
};
rotateScene = (deltaX, deltaY) => {
this.setState({
rotX: this.state.rotX + deltaX / 100,
rotY: this.state.rotY + deltaY / 100,
});
this.drawScene(this.state.gl, this.state.programInfo, this.state.buffers);
};
render() {
return (
<div className="w3-container w3-padding-16">
<canvas
id="glCanvas"
width={1280}
height={720}
onMouseMove={this.onMouseMove}
onMouseDown={this.onMouseDown}
onMouseUp={this.onMouseUp}
></canvas>
</div>
);
}
}
export default Viewer3d;
Fragment Shader:
<script id="fshader" type="x-shader/x-fragment">
precision mediump float;
varying vec4 vColor;
void main(void) {
gl_FragColor = vColor;
}
</script>
Vertex Shader:
<script id="vshader" type="x-shader/x-vertex">
attribute vec4 aVertexPosition;
attribute vec4 aVertexColor;
uniform mat4 uModelViewMatrix;
uniform mat4 uProjectionMatrix;
varying vec4 vColor;
void main(void) {
gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
vColor = aVertexColor;
}
</script>
You need to bind one buffer (say the color one), then use vertexAttribPointer to bind the set buffer to the color attribute. Then again, bind the vertex position buffer, and call vertexAttribPointer to bind it the vertex position attribute. Pseudocode:
gl.bindBuffer(gl.ARRAY_BUFFER, buffers.color);
gl.vertexAttribPointer(programInfo.attribLocations.vertexColor, ...);
gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position);
gl.vertexAttribPointer(programInfo.attribLocations.vertexPosition, ...);

Artefacts when rendering to a framebuffer with alpha blending using WebGL2

I am trying to draw 2D metaballs using WebGL2. I render a bunch of quads with transparent radial gradient and gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) to a separate framebuffer. I then use the resulting texture in a fullscreen quad, where I decide if pixel should be rendered based on it's alpha value like so:
vec4 inputColor = texture(u_texture, v_uv);
float cutoffThreshold = 0.14;
float cutoff = step(cutoffThreshold, inputColor.a);
float threshold = 0.005;
outputColor = mix(
vec4(1, 0, 0, 1),
vec4(0, 0, 1, 1),
cutoff
);
While this kinda works, it produces really noticeable artefacts along the edges:
I think the problem lays in my blending operation. I tried enabling blending only when drawing to my framebuffer and disableing it when rendering my main quad without much success.
Here is my program:
const CONFIG = {
ballsCount: 10,
ballRadius: isMobileBrowser() ? 75 : 200,
gravity: 0.1,
lineWidth: innerWidth / 2,
startVelocityX: { min: 0, max: 0.1 },
startVelocityY: { min: 1, max: 3 },
}
const contentWrapper = document.querySelector('.content')
const canvas = document.createElement('canvas')
const gl = canvas.getContext('webgl2')
const dpr = devicePixelRatio > 2.5 ? 2.5 : devicePixelRatio
if (!gl) {
showWebGL2NotSupported()
}
const lineVertexArrayObject = gl.createVertexArray()
const quadVertexArrayObject = gl.createVertexArray()
const ballsVertexArrayObject = gl.createVertexArray()
const ballsOffsetsBuffer = gl.createBuffer()
let oldTime = 0
let lineAngle = 0
// WebGL Programs
let lineWebGLProgram
let quadWebGLProgram
let ballsWebGLProgram
let quadTextureUniformLoc
let lineAngleUniformLoc
let lineVertexArray
let ballsOffsetsArray
// Not for rendering, just storing the balls velocities
let ballsVelocitiesArray
/* ------- Create horizontal line WebGL program ------- */
{
const vertexShader = makeWebglShader(gl, {
shaderType: gl.VERTEX_SHADER,
shaderSource: `#version 300 es
uniform mat4 u_projectionMatrix;
uniform vec2 u_resolution;
uniform float u_angle;
in vec4 a_position;
mat4 rotationZ( in float angle ) {
return mat4(
cos(angle), -sin(angle), 0.0, 0.0,
sin(angle), cos(angle), 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0
);
}
void main () {
gl_Position = u_projectionMatrix * (rotationZ(u_angle) * a_position + vec4(u_resolution.xy / 2.0, 0.0, 1.0));
}
`,
})
const fragmentShader = makeWebglShader(gl, {
shaderType: gl.FRAGMENT_SHADER,
shaderSource: `#version 300 es
precision highp float;
out vec4 outputColor;
void main () {
outputColor = vec4(0, 0, 1, 1);
}
`,
})
lineWebGLProgram = makeWebglProram(gl, {
vertexShader,
fragmentShader,
})
}
/* ------- Create and assign horizontal line WebGL attributes ------- */
{
lineVertexArray = new Float32Array([-CONFIG.lineWidth / 2, 0, CONFIG.lineWidth / 2, 0])
const vertexBuffer = gl.createBuffer()
const a_position = gl.getAttribLocation(lineWebGLProgram, 'a_position')
gl.bindVertexArray(lineVertexArrayObject)
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
gl.bufferData(gl.ARRAY_BUFFER, lineVertexArray, gl.STATIC_DRAW)
gl.enableVertexAttribArray(a_position)
gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0)
gl.bindVertexArray(null)
}
/* ------- Create metaballs WebGL program ------- */
{
const vertexShader = makeWebglShader(gl, {
shaderType: gl.VERTEX_SHADER,
shaderSource: `#version 300 es
uniform mat4 u_projectionMatrix;
in vec4 a_position;
in vec4 a_offsetPosition;
in vec2 a_uv;
out vec2 v_uv;
void main () {
vec4 correctOffsetedPosition = a_offsetPosition + a_position;
gl_Position = u_projectionMatrix * correctOffsetedPosition;
v_uv = a_uv;
}
`
})
const fragmentShader = makeWebglShader(gl, {
shaderType: gl.FRAGMENT_SHADER,
shaderSource: `#version 300 es
precision highp float;
in vec2 v_uv;
out vec4 outputColor;
void main () {
float dist = distance(v_uv, vec2(0.5));
float c = 0.5 - dist;
outputColor = vec4(vec3(1.0), c);
}
`
})
ballsWebGLProgram = makeWebglProram(gl, {
vertexShader,
fragmentShader,
})
}
/* ------- Create and assign metaballs WebGL attributes ------- */
{
const vertexArray = new Float32Array([
-CONFIG.ballRadius / 2, CONFIG.ballRadius / 2,
CONFIG.ballRadius / 2, CONFIG.ballRadius / 2,
CONFIG.ballRadius / 2, -CONFIG.ballRadius / 2,
-CONFIG.ballRadius / 2, CONFIG.ballRadius / 2,
CONFIG.ballRadius / 2, -CONFIG.ballRadius / 2,
-CONFIG.ballRadius / 2, -CONFIG.ballRadius / 2
])
const uvsArray = makeQuadUVs()
ballsOffsetsArray = new Float32Array(CONFIG.ballsCount * 2)
ballsVelocitiesArray = new Float32Array(CONFIG.ballsCount * 2)
for (let i = 0; i < CONFIG.ballsCount; i++) {
ballsOffsetsArray[i * 2 + 0] = Math.random() * innerWidth
ballsOffsetsArray[i * 2 + 1] = Math.random() * innerHeight
ballsVelocitiesArray[i * 2 + 0] = (Math.random() * 2 - 1) * CONFIG.startVelocityX.max + CONFIG.startVelocityX.min
ballsVelocitiesArray[i * 2 + 1] = Math.random() * CONFIG.startVelocityY.max + CONFIG.startVelocityY.min
}
const vertexBuffer = gl.createBuffer()
const uvsBuffer = gl.createBuffer()
const a_position = gl.getAttribLocation(ballsWebGLProgram, 'a_position')
const a_uv = gl.getAttribLocation(ballsWebGLProgram, 'a_uv')
const a_offsetPosition = gl.getAttribLocation(ballsWebGLProgram, 'a_offsetPosition')
gl.bindVertexArray(ballsVertexArrayObject)
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
gl.bufferData(gl.ARRAY_BUFFER, vertexArray, gl.STATIC_DRAW)
gl.enableVertexAttribArray(a_position)
gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0)
gl.bindBuffer(gl.ARRAY_BUFFER, uvsBuffer)
gl.bufferData(gl.ARRAY_BUFFER, uvsArray, gl.STATIC_DRAW)
gl.enableVertexAttribArray(a_uv)
gl.vertexAttribPointer(a_uv, 2, gl.FLOAT, false, 0, 0)
gl.bindBuffer(gl.ARRAY_BUFFER, ballsOffsetsBuffer)
gl.bufferData(gl.ARRAY_BUFFER, ballsOffsetsArray, gl.DYNAMIC_DRAW)
gl.enableVertexAttribArray(a_offsetPosition)
gl.vertexAttribPointer(a_offsetPosition, 2, gl.FLOAT, false, 0, 0)
gl.vertexAttribDivisor(a_offsetPosition, 1)
gl.bindVertexArray(null)
}
/* ------- Create fullscreen quad WebGL program ------- */
{
const vertexShader = makeWebglShader(gl, {
shaderType: gl.VERTEX_SHADER,
shaderSource: `#version 300 es
uniform mat4 u_projectionMatrix;
in vec4 a_position;
in vec2 a_uv;
out vec2 v_uv;
void main () {
gl_Position = u_projectionMatrix * a_position;
v_uv = a_uv;
}
`
})
const fragmentShader = makeWebglShader(gl, {
shaderType: gl.FRAGMENT_SHADER,
shaderSource: `#version 300 es
precision highp float;
uniform sampler2D u_texture;
in vec2 v_uv;
out vec4 outputColor;
void main () {
vec4 inputColor = texture(u_texture, v_uv);
float cutoffThreshold = 0.14;
float cutoff = step(cutoffThreshold, inputColor.a);
float threshold = 0.005;
outputColor = mix(
vec4(1, 0, 0, 1),
vec4(0, 0, 1, 1),
cutoff
);
cutoffThreshold += 0.001;
cutoff = smoothstep(cutoffThreshold - threshold, cutoffThreshold + threshold, inputColor.a);
outputColor = mix(
outputColor,
vec4(1, 0, 0, 1),
cutoff
);
cutoffThreshold += 0.05;
cutoff = smoothstep(cutoffThreshold - threshold, cutoffThreshold + threshold, inputColor.a);
outputColor = mix(
outputColor,
vec4(0, 1, 0, 1),
cutoff
);
// outputColor = mix(inputColor, mix(baseColor, metaballsColor, cutoff), 0.3);
// outputColor = inputColor;
}
`
})
quadWebGLProgram = makeWebglProram(gl, {
vertexShader,
fragmentShader,
})
}
/* ------- Create and assign fullscreen quad WebGL attributes ------- */
{
const vertexArray = new Float32Array([
0, innerHeight / 2,
innerWidth / 2, innerHeight / 2,
innerWidth / 2, 0,
0, innerHeight / 2,
innerWidth / 2, 0,
0, 0
])
const uvsArray = makeQuadUVs()
const vertexBuffer = gl.createBuffer()
const uvsBuffer = gl.createBuffer()
const a_position = gl.getAttribLocation(quadWebGLProgram, 'a_position')
const a_uv = gl.getAttribLocation(quadWebGLProgram, 'a_uv')
gl.bindVertexArray(quadVertexArrayObject)
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
gl.bufferData(gl.ARRAY_BUFFER, vertexArray, gl.STATIC_DRAW)
gl.enableVertexAttribArray(a_position)
gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0)
gl.bindBuffer(gl.ARRAY_BUFFER, uvsBuffer)
gl.bufferData(gl.ARRAY_BUFFER, uvsArray, gl.STATIC_DRAW)
gl.enableVertexAttribArray(a_uv)
gl.vertexAttribPointer(a_uv, 2, gl.FLOAT, false, 0, 0)
gl.bindVertexArray(null)
}
/* ------- Create WebGL texture to render to ------- */
gl.getExtension('EXT_color_buffer_float')
gl.getExtension('EXT_float_blend')
gl.getExtension('OES_texture_float_linear')
const targetTextureWidth = innerWidth * dpr
const targetTextureHeight = innerHeight * dpr
const targetTexture = gl.createTexture()
gl.bindTexture(gl.TEXTURE_2D, targetTexture)
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, targetTextureWidth, targetTextureHeight, 0, gl.RGBA, gl.FLOAT, null)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_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)
gl.bindTexture(gl.TEXTURE_2D, null)
/* ------- Create WebGL framebuffer to render to ------- */
const framebuffer = gl.createFramebuffer()
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer)
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, targetTexture, 0)
gl.bindFramebuffer(gl.FRAMEBUFFER, null)
init()
function init () {
document.body.appendChild(canvas)
resize()
window.addEventListener('resize', resize)
gl.enable(gl.BLEND)
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
// gl.blendEquation(gl.FUNC_SUBTRACT)
const projectionMatrix = makeProjectionMatrix(innerWidth / 2, innerHeight / 2)
let u_projectionMatrix
gl.useProgram(ballsWebGLProgram)
u_projectionMatrix = gl.getUniformLocation(ballsWebGLProgram, 'u_projectionMatrix')
gl.uniformMatrix4fv(u_projectionMatrix, false, projectionMatrix)
gl.useProgram(null)
gl.useProgram(quadWebGLProgram)
quadTextureUniformLoc = gl.getUniformLocation(quadWebGLProgram, 'u_texture')
gl.uniform1i(quadTextureUniformLoc, 0)
u_projectionMatrix = gl.getUniformLocation(quadWebGLProgram, 'u_projectionMatrix')
gl.uniformMatrix4fv(u_projectionMatrix, false, projectionMatrix)
gl.useProgram(null)
gl.useProgram(lineWebGLProgram)
u_projectionMatrix = gl.getUniformLocation(lineWebGLProgram, 'u_projectionMatrix')
gl.uniformMatrix4fv(u_projectionMatrix, false, projectionMatrix)
const u_resolution = gl.getUniformLocation(lineWebGLProgram, 'u_resolution')
gl.uniform2f(u_resolution, innerWidth, innerHeight)
lineAngleUniformLoc = gl.getUniformLocation(lineWebGLProgram, 'u_angle')
gl.uniform1f(lineAngleUniformLoc, lineAngle * Math.PI / 180)
gl.useProgram(null)
requestAnimationFrame(renderFrame)
}
let a = true
document.addEventListener('click', () => {
a = !a
})
function renderFrame (ts) {
const dt = ts - oldTime
oldTime = ts
for (let i = 0; i < CONFIG.ballsCount; i++) {
ballsVelocitiesArray[i * 2 + 1] += CONFIG.gravity
ballsOffsetsArray[i * 2 + 0] += ballsVelocitiesArray[i * 2 + 0]
ballsOffsetsArray[i * 2 + 1] += ballsVelocitiesArray[i * 2 + 1]
if (ballsOffsetsArray[i * 2 + 0] < CONFIG.ballRadius / 2) {
ballsOffsetsArray[i * 2 + 0] = CONFIG.ballRadius / 2
ballsVelocitiesArray[i * 2 + 0] *= -1
}
if (ballsOffsetsArray[i * 2 + 0] > innerWidth - CONFIG.ballRadius / 2) {
ballsOffsetsArray[i * 2 + 0] = innerWidth - CONFIG.ballRadius / 2
ballsVelocitiesArray[i * 2 + 0] *= -1
}
if (ballsOffsetsArray[i * 2 + 1] - CONFIG.ballRadius > innerHeight) {
ballsOffsetsArray[i * 2 + 1] = -CONFIG.ballRadius
ballsVelocitiesArray[i * 2 + 1] = 5 + Math.random() * 3
}
}
checkLine()
gl.bindBuffer(gl.ARRAY_BUFFER, ballsOffsetsBuffer)
gl.bufferData(gl.ARRAY_BUFFER, ballsOffsetsArray, gl.DYNAMIC_DRAW)
if (a) {
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer)
}
gl.viewport(0, 0, targetTextureWidth, targetTextureHeight)
gl.clearColor(0.1, 0.1, 0.1, 0)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.bindVertexArray(ballsVertexArrayObject)
gl.useProgram(ballsWebGLProgram)
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, CONFIG.ballsCount)
gl.bindVertexArray(null)
gl.bindFramebuffer(gl.FRAMEBUFFER, null)
gl.viewport(0, 0, canvas.width, canvas.height)
if (a) {
gl.bindVertexArray(quadVertexArrayObject)
gl.useProgram(quadWebGLProgram)
gl.bindTexture(gl.TEXTURE_2D, targetTexture)
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true)
gl.drawArrays(gl.TRIANGLES, 0, 6)
gl.useProgram(null)
gl.bindVertexArray(null)
gl.bindTexture(gl.TEXTURE_2D, null)
}
lineAngle = Math.sin(ts * 0.001) * 30
gl.bindVertexArray(lineVertexArrayObject)
gl.useProgram(lineWebGLProgram)
gl.uniform1f(lineAngleUniformLoc, -lineAngle * Math.PI / 180)
gl.drawArrays(gl.LINES, 0, 2)
gl.useProgram(null)
gl.bindVertexArray(null)
requestAnimationFrame(renderFrame)
}
function getLineBounds () {
const x1 = lineVertexArray[0]
const y1 = lineVertexArray[1]
const x2 = lineVertexArray[2]
const y2 = lineVertexArray[3]
if (lineAngle === 0) {
const minX = Math.min(x1, x2)
const minY = Math.min(y1, y2)
const maxX = Math.max(x1, x2)
const maxY = Math.max(y1, y2)
return {
x: x1 + minX,
y: y1 + minY,
width: maxX - minX,
height: maxY - minY,
}
} else {
const rotation = lineAngle * Math.PI / 180
const sin = Math.sin(rotation)
const cos = Math.cos(rotation)
const x1r = cos * x1 + sin * y1
const x2r = cos * x2 + sin * y2
const y1r = cos * y1 + sin * x1
const y2r = cos * y2 + sin * x2
const x = innerWidth / 2 + x1 + Math.min(x1r, x2r) + CONFIG.lineWidth / 2
const y = innerHeight / 2 + y1 + Math.min(y1r, y2r) + CONFIG.lineWidth / 2
const width = Math.max(x1r, x2r) - Math.min(x1r, x2r)
const height = Math.max(y1r, y2r) - Math.min(y1r, y2r)
return {
x,
y,
width,
height,
}
}
}
function checkLine () {
const lineBounds = getLineBounds()
const ballRadius = CONFIG.ballRadius / 7
for (let i = 0; i < CONFIG.ballsCount; i++) {
const ballx = ballsOffsetsArray[i * 2 + 0]
const bally = ballsOffsetsArray[i * 2 + 1]
const ballvx = ballsVelocitiesArray[i * 2 + 0]
const ballvy = ballsVelocitiesArray[i * 2 + 1]
if (ballx + ballRadius / 2 > lineBounds.x && ballx - ballRadius / 2 < lineBounds.x + lineBounds.width) {
const lineRotation = lineAngle * Math.PI / 180
const cos = Math.cos(lineRotation)
const sin = Math.sin(lineRotation)
let x = ballx - innerWidth / 2
let y = bally - innerHeight / 2
let vx1 = cos * ballvx + sin * ballvy
let vy1 = cos * ballvy - sin * ballvx
let y1 = cos * y - sin * x
if (y1 > -ballRadius / 2 && y1 < vy1) {
// debugger
const x2 = cos * x + sin * y
y1 = -ballRadius / 2
vy1 *= -0.45
x = cos * x2 - sin * y1
y = cos * y1 + sin * x2
ballsVelocitiesArray[i * 2 + 0] = cos * vx1 - sin * vy1
ballsVelocitiesArray[i * 2 + 1] = cos * vy1 + sin * vx1
ballsOffsetsArray[i * 2 + 0] = innerWidth / 2 + x
ballsOffsetsArray[i * 2 + 1] = innerHeight / 2 + y
}
}
}
}
function resize () {
canvas.width = innerWidth * dpr
canvas.height = innerHeight * dpr
canvas.style.width = `${innerWidth}px`
canvas.style.height = `${innerHeight}px`
}
/* ------- WebGL helpers ------- */
function makeQuadUVs () {
return new Float32Array([0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1])
}
function makeWebglShader (gl, { shaderType, shaderSource }) {
const shader = gl.createShader(shaderType)
gl.shaderSource(shader, shaderSource)
gl.compileShader(shader)
const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS)
if (success) {
return shader
}
console.error(`
Error in ${shaderType === gl.VERTEX_SHADER ? 'vertex' : 'fragment'} shader:
${gl.getShaderInfoLog(shader)}
`)
gl.deleteShader(shader)
}
function makeWebglProram (gl, { vertexShader, fragmentShader }) {
const program = gl.createProgram()
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
gl.linkProgram(program)
const success = gl.getProgramParameter(program, gl.LINK_STATUS)
if (success) {
return program
}
console.error(gl.getProgramInfoLog(program))
gl.deleteProgram(program)
}
function makeProjectionMatrix (width, height) {
return new Float32Array([
2 / width, 0, 0, 0,
0, -2 / height, 0, 0,
0, 0, 0, 0,
-1, 1, 0, 1,
])
}
function showWebGL2NotSupported () {
const errorMessageWrapper = document.createElement('div')
if (isIOS()) {
const iOSVersion = getIOSVersion().major
if (iOSVersion === 13) {
errorMessageWrapper.innerHTML = `
<p>Please update your device to iOS / iPadOS 14 so you can see this demo.</p>
`
} else if (iOSVersion === 14) {
errorMessageWrapper.innerHTML = `
<p>In order to see WebGL2 content, you need to enable it from your device settings.</p>
<p>Settings > Safari > Advanced > Experimental Features > WebGL2.0</p>
`
}
} else {
errorMessageWrapper.innerHTML = `
<h1>Your browser does not support WebGL2</h1>
<p>Please try one of these alternative browsers:</p>
<ul>
<li>Microsoft Edge (version 79+)</li>
<li>Mozilla Firefox (version 51+)</li>
<li>Google Chrome (version 56+)</li>
<li>Opera (version 43+)</li>
</ul>
`
}
errorMessageWrapper.classList.add('webgl2-error')
document.body.appendChild(errorMessageWrapper)
}
/* ------- Generic helpers ------- */
function isMobileBrowser () {
return (function (a) {
if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) {
return true
}
return false
})(navigator.userAgent || navigator.vendor || window.opera)
}
function isIOS () {
return (/AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent)) || isIPadOS()
}
function isIPadOS () {
return navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1 && !window.MSStream
}
function getIOSVersion () {
const found = navigator.userAgent.match(/(iPhone|iPad); (CPU iPhone|CPU) OS (\d+)_(\d+)(_(\d+))?\s+/)
if (!found || found.length < 4) {
return {
major: 0,
minor: 0
}
}
return {
major: parseInt(found[3], 10),
minor: parseInt(found[4], 10)
}
}
* { margin: 0; padding: 0; }
EDIT
By the first comment suggestion, I enabled the EXT_color_buffer_float and OES_texture_float_linear extensions and augmented my texImage2D call like this:
gl.getExtension('EXT_color_buffer_float')
gl.getExtension('OES_texture_float_linear')
// ...
const targetTexture = gl.createTexture()
gl.bindTexture(gl.TEXTURE_2D, targetTexture)
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, targetTextureWidth, targetTextureHeight, 0, gl.RGBA, gl.FLOAT, null)
Unfortunately this results in corrupted rendering on all of my browsers running on MacOS. I updated my code snippet to reflect the new changes.
I read in MDN's Webgl2 best practices that EXT_float_blend is also required in order to allow blending in framebuffers, but is implicitly allowed when requesting EXT_color_buffer_float?
On a sidenote, I guess the OES_texture_float_linear would not work on mobile devices? So if the extension is not available, I would need to fallback to 8bit RGBA textures?
Thanks!
I'm pretty sure the issue the texture your rendering to is 8bits. Switch it to a floating point texture (RGBA32F) You'll need to check for and enable EXT_color_buffer_float and OES_texture_float_linear
Update
You say it won't work on mobile but you're using WebGL2 which hasn't shipped on iPhone yet (2021/1/3). As for RGBA32F not being renderable on mobile you could try RGBA16F. You'll have to check for and enable the corresponding extensions, EXT_color_buffer_half_float and OES_texture_half_float_linear. Your current code is not checking that the extensions actually exist (I'm assuming that was just to keep the code minimal)
The corruption is that your circle calculation draws alpha < 0 outside the circle but inside the quad. Before that was getting clipped to 0 because of the texture format but now with floating point textures it's not so it affects other circles.
Either discard if c <= 0 or clamp so it doesn't go below 0.
Note: you might find coloring faster and more flexible using a ramp texture. example, example2
Also note: It would have been nice if you'd created a more minimal repo. There's no need for the animation to show either issue
Update 2
Something else to point out, maybe you already knew this, but, the circle calculation
float dist = distance(v_uv, vec2(0.5));
float c = 0.5 - dist;
means the max value is 0.5 so half the range is being thrown away.
Switching to this
float dist = distance(v_uv, vec2(0.5)) * 2.0;
float c = 1.0 - dist;
Changes the range from 0 to 1 which is arguably better for shading later and to not throw away a bunch of precision in the texture. Example

GLSL shader for texture cubic projection

I am trying to implement a texture cubic projection inside my WebGL shader, like in the picture below:
What I tried so far:
I am passing the bounding box of my object (the box in the middle of the picture) as follows:
uniform vec3 u_bbmin;
uniform vec3 u_bbmax;
... so the eight vertexes of my projection box are:
vec3 v1 = vec3(u_bbmin.x, u_bbmin.y, u_bbmin.z);
vec3 v2 = vec3(u_bbmax.x, u_bbmin.y, u_bbmin.z);
vec3 v3 = vec3(u_bbmin.x, u_bbmax.y, u_bbmin.z);
...other combinations
vec3 v8 = vec3(u_bbmax.x, u_bbmax.y, u_bbmax.z);
At the end, to sample from my texture I need a map in the form of:
varying vec3 v_modelPos;
...
uniform sampler2D s_texture;
vec2 tCoords = vec2(0.0);
tCoords.s = s(x,y,z)
tCoords.t = t(y,y,z)
vec4 color = texture2D(s_texture, tCoords);
I was able to implement spherical and cylindrical projections, but I am stuck now how to get this kind of cubic map, The texture shall stretch to the whole bounding box, aspect ratio doesn't matter.
Maybe I am missing some key points and I need some hints. How should the math for a cubic projection looks like?
I honestly don't know if this is correct or not but ...
Looking up how cube mapping works there's a table in the OpenGL ES 2.0 spec
Major Axis Direction| Target |sc |tc |ma |
--------------------+---------------------------+---+---+---+
+rx |TEXTURE_CUBE_MAP_POSITIVE_X|−rz|−ry| rx|
−rx |TEXTURE_CUBE_MAP_NEGATIVE_X| rz|−ry| rx|
+ry |TEXTURE_CUBE_MAP_POSITIVE_Y| rx| rz| ry|
−ry |TEXTURE_CUBE_MAP_NEGATIVE_Y| rx|−rz| ry|
+rz |TEXTURE_CUBE_MAP_POSITIVE_Z| rx|−ry| rz|
−rz |TEXTURE_CUBE_MAP_NEGATIVE_Z|−rx|−ry| rz|
--------------------+---------------------------+---+---+---+
Table 3.21: Selection of cube map images based on major axis direction of texture coordinates
Using that I wrote this function
#define RX 0
#define RY 1
#define RZ 2
#define S 0
#define T 1
void majorAxisDirection(vec3 normal, inout mat4 uvmat) {
vec3 absnorm = abs(normal);
if (absnorm.x > absnorm.y && absnorm.x > absnorm.z) {
// x major
if (normal.x >= 0.0) {
uvmat[RZ][S] = -1.;
uvmat[RY][T] = -1.;
} else {
uvmat[RZ][S] = 1.;
uvmat[RY][T] = -1.;
}
} else if (absnorm.y > absnorm.z) {
// y major
if (normal.y >= 0.0) {
uvmat[RX][S] = 1.;
uvmat[RZ][T] = 1.;
} else {
uvmat[RX][S] = 1.;
uvmat[RZ][T] = -1.;
}
} else {
// z major
if (normal.z >= 0.0) {
uvmat[RX][S] = 1.;
uvmat[RY][T] = -1.;
} else {
uvmat[RX][S] = -1.;
uvmat[RY][T] = -1.;
}
}
}
You pass in a matrix and it sets it up to move the correct X, Y, or Z to the X and Y columns (to convert to s and t). In other words you pass in normal and it returns s and t.
This would effectively give a unit cube projected on the positive side of the origin. Adding in another matrix we can move and scale that cube.
If you want it to fit the cube exactly then you need to set the scale, translation and orientation to match the cube.
"use strict";
/* global document, twgl, requestAnimationFrame */
const vs = `
uniform mat4 u_model;
uniform mat4 u_viewProjection;
attribute vec4 position;
attribute vec3 normal;
attribute vec2 texcoord;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_position;
void main() {
v_texCoord = texcoord;
vec4 position = u_model * position;
gl_Position = u_viewProjection * position;
v_position = position.xyz;
v_normal = (u_model * vec4(normal, 0)).xyz;
}
`;
const fs = `
precision mediump float;
varying vec3 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
uniform mat4 u_cubeProjection;
uniform sampler2D u_diffuse;
#define RX 0
#define RY 1
#define RZ 2
#define S 0
#define T 1
#if BOX_PROJECTION
void majorAxisDirection(vec3 normal, inout mat4 uvmat) {
vec3 absnorm = abs(normal);
if (absnorm.x > absnorm.y && absnorm.x > absnorm.z) {
// x major
if (normal.x >= 0.0) {
uvmat[RZ][S] = -1.;
uvmat[RY][T] = -1.;
} else {
uvmat[RZ][S] = 1.;
uvmat[RY][T] = -1.;
}
} else if (absnorm.y > absnorm.z) {
// y major
if (normal.y >= 0.0) {
uvmat[RX][S] = 1.;
uvmat[RZ][T] = 1.;
} else {
uvmat[RX][S] = 1.;
uvmat[RZ][T] = -1.;
}
} else {
// z major
if (normal.z >= 0.0) {
uvmat[RX][S] = 1.;
uvmat[RY][T] = -1.;
} else {
uvmat[RX][S] = -1.;
uvmat[RY][T] = -1.;
}
}
}
#else // cube projection
void majorAxisDirection(vec3 normal, inout mat4 uvmat) {
vec3 absnorm = abs(normal);
if (absnorm.x > absnorm.y && absnorm.x > absnorm.z) {
// x major
uvmat[RZ][S] = 1.;
uvmat[RY][T] = -1.;
} else if (absnorm.y > absnorm.z) {
uvmat[RX][S] = 1.;
uvmat[RZ][T] = 1.;
} else {
uvmat[RX][S] = 1.;
uvmat[RY][T] = -1.;
}
}
#endif
void main() {
vec3 normal = normalize(v_normal);
mat4 uvmat = mat4(
vec4(0, 0, 0, 0),
vec4(0, 0, 0, 0),
vec4(0, 0, 0, 0),
vec4(0, 0, 0, 1));
majorAxisDirection(normal, uvmat);
uvmat = mat4(
abs(uvmat[0]),
abs(uvmat[1]),
abs(uvmat[2]),
abs(uvmat[3]));
vec2 uv = (uvmat * u_cubeProjection * vec4(v_position, 1)).xy;
gl_FragColor = texture2D(u_diffuse, uv);
}
`;
const m4 = twgl.m4;
const gl = twgl.getWebGLContext(document.getElementById("c"));
// compile shaders, look up locations
const cubeProjProgramInfo = twgl.createProgramInfo(gl,
[vs, '#define BOX_PROJECTION 0\n' + fs]);
const boxProjProgramInfo = twgl.createProgramInfo(gl,
[vs, '#define BOX_PROJECTION 1\n' + fs]);
let progNdx = 1;
const programInfos = [
cubeProjProgramInfo,
boxProjProgramInfo,
];
// create buffers
const cubeBufferInfo = twgl.primitives.createCubeBufferInfo(gl, 2);
const sphereBufferInfo = twgl.primitives.createSphereBufferInfo(gl, 1, 60, 40);
const ctx = document.createElement("canvas").getContext("2d");
ctx.canvas.width = 256;
ctx.canvas.height = 256;
ctx.fillStyle = `hsl(${360}, 0%, 30%)`;
ctx.fillRect(0, 0, 256, 256);
for (let y = 0; y < 4; ++y) {
for (let x = 0; x < 4; x += 2) {
ctx.fillStyle = `hsl(${(x + y) / 16 * 360}, 100%, 75%)`;
ctx.fillRect((x + (y & 1)) * 64, y * 64, 64, 64);
}
}
ctx.lineWidth = 10;
ctx.strokeRect(0, 0, 256, 256);
ctx.font = "240px sans-serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = 'red';
ctx.fillText("F", 128, 128);
const texture = twgl.createTexture(gl, {
src: ctx.canvas,
wrap: gl.CLAMP_TO_EDGE,
min: gl.LINEAR, // no mips
});
function addElem(parent, type) {
const elem = document.createElement(type);
parent.appendChild(elem);
return elem;
}
function makeRange(parent, obj, prop, min, max, name) {
const divElem = addElem(parent, 'div');
const inputElem = addElem(divElem, 'input');
Object.assign(inputElem, {
type: 'range',
min: 0,
max: 1000,
value: (obj[prop] - min) / (max - min) * 1000,
});
const valueElem = addElem(divElem, 'span');
valueElem.textContent = obj[prop].toFixed(2);
const labelElem = addElem(divElem, 'label');
labelElem.textContent = name;
function update() {
inputElem.value = (obj[prop] - min) / (max - min) * 1000,
valueElem.textContent = obj[prop].toFixed(2);
}
inputElem.addEventListener('input', (e) => {
obj[prop] = (e.target.value / 1000 * (max - min) + min);
update();
});
return update;
}
const models = [
cubeBufferInfo,
sphereBufferInfo,
cubeBufferInfo,
];
const rotateSpeeds = [
1,
1,
0,
];
let modelNdx = 0;
const ui = document.querySelector('#ui');
const cubeMatrix = m4.translation([0.5, 0.5, 0.5]);
const updaters = [
makeRange(ui, cubeMatrix, 0, -2, 2, 'sx'),
makeRange(ui, cubeMatrix, 5, -2, 2, 'sy'),
makeRange(ui, cubeMatrix, 10, -2, 2, 'sz'),
makeRange(ui, cubeMatrix, 12, -2, 2, 'tx'),
makeRange(ui, cubeMatrix, 13, -2, 2, 'ty'),
makeRange(ui, cubeMatrix, 14, -2, 2, 'tz'),
];
document.querySelectorAll('input[name=shape]').forEach((elem) => {
elem.addEventListener('change', (e) => {
if (e.target.checked) {
modelNdx = parseInt(e.target.value);
if (modelNdx == 2) {
m4.scaling([1/2, 1/2, 1/2], cubeMatrix);
m4.translate(cubeMatrix, [1, 1, 1], cubeMatrix);
updaters.forEach(f => f());
}
}
})
});
document.querySelectorAll('input[name=proj]').forEach((elem) => {
elem.addEventListener('change', (e) => {
if (e.target.checked) {
progNdx = parseInt(e.target.value);
}
})
});
const uniforms = {
u_diffuse: texture,
u_cubeProjection: cubeMatrix,
};
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
const programInfo = programInfos[progNdx];
const bufferInfo = models[modelNdx];
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
const fov = 30 * Math.PI / 180;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.5;
const zFar = 10;
const projection = m4.perspective(fov, aspect, zNear, zFar);
const eye = [0, 4, -4];
const target = [0, 0, 0];
const up = [0, 1, 0];
const camera = m4.lookAt(eye, target, up);
const view = m4.inverse(camera);
const viewProjection = m4.multiply(projection, view);
const model = m4.rotationY(time * rotateSpeeds[modelNdx]);
uniforms.u_viewProjection = viewProjection;
uniforms.u_model = model;
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;
font-family: monospace;
color: white;
}
canvas {
display: block;
width: 100vw;
height: 100vh;
background: #444;
}
#ui {
position: absolute;
left: 0;
top: 0;
}
#ui span {
display: inline-block;
width: 4em;
text-align: right;
}
<canvas id="c"></canvas>
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
<div id="ui">
<div>
<input type="radio" name="proj" id="sphere" value="0">
<label for="sphere">cubic projection</label>
<input type="radio" name="proj" id="cube" value="1" checked>
<label for="cube">box projection</label>
</div>
<div>
<input type="radio" name="shape" id="sphere" value="1">
<label for="sphere">sphere</label>
<input type="radio" name="shape" id="cube" value="0" checked>
<label for="cube">cube</label>
<input type="radio" name="shape" id="cube" value="2">
<label for="cube">cube match</label>
</div>
</div>
The key-point here is: normals shall be in object-space. Please note that gman's answer is more elegant than mine, by using a matrix for the uv computation. I am using instead the bounding box coordinates, which are already passed to the vertex shader as uniform for other general purposes.
Moreover, I don't even need to distinguish all the six major axis, I just only need three sides projection, so this can be simplified down. Of course, the texture will be mirrored on the opposite faces.
float sX = u_bbmax.x - u_bbmin.x;
float sY = u_bbmax.y - u_bbmin.y;
float sZ = u_bbmax.z - u_bbmin.z;
/* --- BOX PROJECTION - THREE SIDES --- */
if( (abs(modelNormal.x) > abs(modelNormal.y)) && (abs(modelNormal.x) > abs(modelNormal.z)) ) {
uvCoords = modelPos.yz / vec2(sY, -sZ); // X axis
} else if( (abs(modelNormal.z) > abs(modelNormal.x)) && (abs(modelNormal.z) > abs(modelNormal.y)) ) {
uvCoords = modelPos.xy / vec2(sX, -sY); // Z axis
} else {
uvCoords = modelPos.xz / vec2(sX, -sZ); // Y axis
}
uvCoords += vec2(0.5);
Explanation:
The direction of the texture projection is determined by the
order of the modelPos coordinates.
Example: the texture can be rotated by 90 degrees by using
modelPos.yx instead of modelPos.xy.
The orientation of the texture projection is determined by the sign of
the modelPos coordinates.
Example: the texture can be mirrored on the Y-axis by using
vec2(sX, sY) instead of vec2(sX, -sY).
Result:
EDIT:
It is worth to link here another answer from gman which contain additional information about this topic and also some cool optimization techniques to avoid conditionals inside GLSL shaders: How to implement textureCube using 6 sampler2D.

How draw 2d array of points being every point a different color at one time with WebGl?

With this code i can draw pixel by pixel, but i want to send all array together for a performance issue.
function drawOneBlackPixel( gl, x, y ) {
// Fills the buffer with a single point?
r=numToFloat(_.random(1,255) );
g=numToFloat(_.random(1,255) );
b=numToFloat(_.random(1,255) );
gl.uniform4f( colorLocation, r , g, b, 1);
gl.bufferData( gl.ARRAY_BUFFER , new Float32Array([
x, y]), gl.STATIC_DRAW );
gl.drawArrays( gl.POINTS, 0, 1 );
// Draw one point.
}
function numToFloat(x){
return x/255;
}
for(i=0;i<500;i++){
for(j=0;j<500;j++){
drawOneBlackPixel( gl, i, j );
}
}
Thanks!
Just like XY data, you can pass color data in attributes
const vs = `
attribute vec2 aPosition;
attribute vec3 aColor;
varying vec3 vColor;
void main() {
gl_Position = vec4( aPosition, 1.0, 1.0 );
vColor = aColor;
gl_PointSize = 4.0;
}
`;
const fs = `
precision mediump float;
varying vec3 vColor;
void main() {
gl_FragColor = vec4( vColor, 1.0 );
}
`;
const gl = document.querySelector("#glcanvas").getContext("webgl");
// Compile program
// ===================
var program = gl.createProgram();
var vShader = gl.createShader( gl.VERTEX_SHADER );
var fShader = gl.createShader( gl.FRAGMENT_SHADER );
gl.attachShader( program, vShader );
gl.attachShader( program, fShader );
gl.shaderSource( vShader, vs );
gl.compileShader( vShader );
gl.shaderSource( fShader, fs );
gl.compileShader( fShader );
gl.linkProgram( program );
gl.useProgram( program );
// Create points datas
// ===================
const NUM_POINTS = 0xFF;
var data = new Float32Array( NUM_POINTS * 5);
for (var i = 0; i < NUM_POINTS; i++) {
var j = i*5;
data[j+0] = Math.random()*2.0 - 1.0; // x
data[j+1] = Math.random()*2.0 - 1.0; // y
data[j+2] = Math.random(); // r
data[j+3] = Math.random(); // g
data[j+4] = Math.random(); // b
};
// Setup ArrayBuffer
// ===================
var buffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, this.buffer );
gl.bufferData( gl.ARRAY_BUFFER, data, gl.STATIC_DRAW );
var aPos = gl.getAttribLocation( program, 'aPosition' );
gl.enableVertexAttribArray( aPos );
gl.vertexAttribPointer( aPos, 2, gl.FLOAT, false, 5*4, 0 );
var aCol = gl.getAttribLocation( program, 'aColor' );
gl.enableVertexAttribArray( aCol );
gl.vertexAttribPointer( aCol, 3, gl.FLOAT, false, 5*4, 2*4 );
// Draw
// ===================
gl.drawArrays( gl.POINTS, 0, NUM_POINTS );
canvas { border: 1px solid black; margin: 2px; }
<canvas id="glcanvas"></canvas>

Draw Multiple Shapes in WebGL

I am facing the same issue of creating multiple objects ( One rotating and One static). I want to draw a static rectangle in the following code (The rotating rectangle code is from Edward Angels WebGL examples). I try to follow the instructions that gman has said from the link Drawing many shapes in WebGL, but still not able to solve. It would be nice to get some help on this to create another static object such as rectangle along with this rotating rectangle in the code. Thanks.
<script id="vertex-shader" type="x-shader/x-vertex">
attribute vec4 vPosition;
uniform float theta;
void
main()
{
float s = sin( theta );
float c = cos( theta );
gl_Position.x = -s * vPosition.x + c * vPosition.y;
gl_Position.y = s * vPosition.y + c * vPosition.x;
gl_Position.z = 0.0;
gl_Position.w = 1.0;
}
</script>
<script id="fragment-shader" type="x-shader/x-fragment">
precision mediump float;
void
main()
{
gl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );
}
</script>
var canvas;
var gl;
var theta = 0.0;
var thetaLoc;
window.onload = function init()
{
canvas = document.getElementById( "gl-canvas" );
gl = WebGLUtils.setupWebGL( canvas );
if ( !gl ) { alert( "WebGL isn't available" ); }
gl.viewport( 0, 0, canvas.width, canvas.height );
gl.clearColor( 1.0, 1.0, 1.0, 1.0 );
var program = initShaders( gl, "vertex-shader", "fragment-shader" );
gl.useProgram( program );
var vertices = [
vec2( 0, 1 ),
vec2( 1, 0 ),
vec2( -1, 0 ),
vec2( 0, -1 )
];
var bufferId = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, bufferId );
gl.bufferData( gl.ARRAY_BUFFER, flatten(vertices), gl.STATIC_DRAW );
var vPosition = gl.getAttribLocation( program, "vPosition" );
gl.vertexAttribPointer( vPosition, 2, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( vPosition );
thetaLoc = gl.getUniformLocation( program, "theta" );
render();
};
function render() {
gl.clear( gl.COLOR_BUFFER_BIT );
theta += 0.1;
gl.uniform1f( thetaLoc, theta );
gl.drawArrays( gl.TRIANGLE_STRIP, 0, 4 );
window.requestAnimFrame(render);
}
So, were you define your vertices for the rectangle, simply add on more vertices to create an additional rectangle at a different point on the canvas. This one you only need to draw once, so if you have say:
var render = function(){
//Insert rest of code
if(!not_First_Time)
{
gl.drawArrays(gl.TRIANGLE_STRIP , 4 , 8 );
}
gl.drawArrays( gl.TRIANGLE_STRIP, 0, 4 );
//Insert rest of code
But that's more or less the cheating way of doing it as you're still modifying the points to rotate, and if you ever re-drew them, they would rotate as the main square did.
I also notice you stripped out a few of the include commands from the HTML code. You're going to need those.
Here is what my solution...
SHADER CODE:
<script id="vertex-shader" type="x-shader/x-vertex">
attribute vec4 vPosition;
uniform float theta;
void
main()
{
float s = sin( theta );
float c = cos( theta );
gl_Position.x = -s * vPosition.x + c * vPosition.y;
gl_Position.y = s * vPosition.y + c * vPosition.x;
gl_Position.z = 0.0;
gl_Position.w = 1.0;
}
</script>
<script id="fragment-shader" type="x-shader/x-fragment">
precision mediump float;
void
main()
{
gl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );
}
</script>
JAVASCRIPT CODE:
var canvas;
var gl;
var theta = 0.0;
var thetaLoc;
var program;
var program1;
window.onload = function init()
{
canvas = document.getElementById( "gl-canvas" );
gl = WebGLUtils.setupWebGL( canvas );
if ( !gl ) { alert( "WebGL isn't available" ); }
//
// Configure WebGL
//
gl.viewport( 0, 0, canvas.width, canvas.height );
gl.clearColor( 1.0, 1.0, 1.0, 1.0 );
// Load shaders and initialize attribute buffers
program = initShaders( gl, "vertex-shader", "fragment-shader" );
program1 = initShaders( gl, "vertex-shader", "fragment-shader" );
//Rotating Rectangle
var rr_vertices = [
vec2( 0, 0.25),
vec2( 0.25, 0),
vec2(-0.25, 0 ),
vec2( 0, -0.25)
];
// Load the data into the GPU
rr_bufferId = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, rr_bufferId );
gl.bufferData( gl.ARRAY_BUFFER, flatten(rr_vertices), gl.STATIC_DRAW );
// Associate out shader variables with our data buffer
rr_vPosition = gl.getAttribLocation( program, "vPosition" );
gl.vertexAttribPointer( rr_vPosition, 2, gl.FLOAT, false, 0, 0 );
//Static Rectangle
var sr_vertices = [
vec2( 0.5, 0.5),
vec2( 1.0, 0.5),
vec2( 0.5, 1.0 ),
vec2( 1.0, 1.0)
];
// Load the data into the GPU
sr_bufferId = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, sr_bufferId );
gl.bufferData( gl.ARRAY_BUFFER, flatten(sr_vertices), gl.STATIC_DRAW );
// Associate out shader variables with our data buffer
sr_vPosition = gl.getAttribLocation( program, "vPosition" );
render();
};
var rr_vPosition;
var sr_vPosition;
var rr_bufferId;
var sr_bufferId;
function render() {
gl.clear( gl.COLOR_BUFFER_BIT );
gl.useProgram( program1 );
gl.enableVertexAttribArray( sr_vPosition );
gl.bindBuffer( gl.ARRAY_BUFFER, sr_bufferId );
gl.vertexAttribPointer( sr_vPosition, 2, gl.FLOAT, false, 0, 0 );
gl.drawArrays( gl.TRIANGLE_STRIP, 0, 4 );
gl.useProgram( program );
thetaLoc = gl.getUniformLocation( program, "theta" );
gl.enableVertexAttribArray( rr_vPosition );
gl.bindBuffer( gl.ARRAY_BUFFER, rr_bufferId );
gl.vertexAttribPointer( rr_vPosition, 2, gl.FLOAT, false, 0, 0 );
theta += 0.1;
gl.uniform1f( thetaLoc, theta );
gl.drawArrays( gl.TRIANGLE_STRIP, 0, 4 );
window.requestAnimFrame(render);
}

Resources