Related
I have this render which is adapted from glsl sandbox. I've poked and struggled to get it this far, but currently its rendering nicely apart from one issue. Heres the code:
<canvas class="glslCanvas" id="canvas" width="500px" height="100%"></canvas>
<style>
.glslCanvas {
top: 0;
left: 0;
height: 100%;
z-index: -1;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r122/three.min.js"></script>
<script>
"use strict";
var CustomScene = /** #class */function () {
function CustomScene() {
}
CustomScene.prototype.init = function (canvas) {
this.scene = new THREE.Scene();
this.scene.background = new THREE.Color(0xe3e3e3);
this.light = new THREE.SpotLight(0xffffff, 1);
var fov = 90;
var aspect = (window.innerWidth) / window.innerHeight;
var near = 1;
var far = 100;
this.camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
this.geometry = new THREE.PlaneBufferGeometry(30, 10);
this.material = new THREE.ShaderMaterial({
vertexShader: "precision mediump float;\nvarying vec2 vUv;\n \nvoid main() {\nvUv = uv;\ngl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}",
fragmentShader: "\n#ifdef GL_ES\n precision mediump float;\n#endif\n\nuniform float uTime;\nuniform vec2 uResolution;\n\nvoid main( void ) \n{\n\tvec2 p = ( gl_FragCoord.xy / uResolution.xy ) * 1.8 - 1.0;\n\t\n\tvec3 c = vec3( 0.0 );\n\t\n\tfloat amplitude = 0.15; \n\tfloat glowT = sin(uTime) * 0.5 + 0.5;\n\tfloat glowFactor = mix( 0.05, 0.2, glowT );\n\tc += vec3(0.05, 0.02, 0.01) * ( glowFactor * abs( 1.0 / sin(p.x + sin( p.y + uTime ) * amplitude ) ));\n\tc += vec3(0.05, 0.02, 0.01) * ( glowFactor * abs( 1.0 / sin(p.x + sin( p.y + uTime+1.00 ) * amplitude+0.1 ) ));\n\tc += vec3(0.05, 0.02, 0.01) * ( glowFactor * abs( 1.0 / sin(p.x + sin( p.y + uTime+2.00 ) * amplitude+0.2 )));\n\tc += vec3(0.05, 0.02, 0.01) * ( glowFactor * abs( 1.0 / sin(p.x + sin( p.y + uTime+3.00 ) * amplitude+0.3 )));\n\tc += vec3(0.05, 0.02, 0.01) * ( glowFactor * abs( 1.0 / sin(p.x + sin( p.y + uTime+4.00 ) * amplitude+0.4 )));\n\n\n\tc += vec3(0.05, 0.02, 0.01) * ( glowFactor * abs( 1.0 / sin(p.x + sin( p.y + uTime+5.00 ) * amplitude+0.5 )));\n\n\n\n\tgl_FragColor = vec4( c, 0.2 );\n}",
uniforms: {
uTime: { value: 0.0 },
uResolution: { value: { x: window.innerWidth * 1.2, y: window.innerHeight } },
uMouse: { value: { x: 0, y: 0 } },
uColor: { value: new THREE.Color(0xffffff) } } });
this.mesh = new THREE.Mesh(this.geometry, this.material);
this.renderer = new THREE.WebGLRenderer({
canvas: canvas });
this.renderer.setClearColor(0xffffff, 1);
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setSize(window.innerWidth , window.innerHeight);
this.scene.add(this.camera);
this.scene.add(this.mesh);
this.scene.add(this.light);
this.mesh.position.set(0, 0, 0);
this.camera.position.set(0, 0, 1);
this.light.position.set(0, 0, 10);
this.light.lookAt(this.mesh.position);
this.camera.lookAt(this.mesh.position);
this.clock = new THREE.Clock();
this.addEvents();
};
CustomScene.prototype.run = function () {
window.requestAnimationFrame(this.run.bind(this));
this.material.uniforms.uTime.value = this.clock.getElapsedTime();
this.renderer.render(this.scene, this.camera);
};
CustomScene.prototype.addEvents = function () {
window.addEventListener("resize", this.onResize.bind(this), false);
};
CustomScene.prototype.onResize = function () {
this.material.uniforms.uResolution = {
value: { x: window.innerWidth, y: window.innerHeight } };
this.camera.aspect = (window.innerWidth) / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
};
return CustomScene;
}();
var scene = new CustomScene();
scene.init(document.getElementById("canvas"));
scene.run();
</script>
This works perfectly but on mobile and on big screens/resize it repeats. what am I doing wrong for this to happen?
the best solution is for only one spiral to appear, currently, it seems two (or sometimes 3) appear.
Sam
It looks like you set the width of the canvas to 500px , whereas you give the renderer and the uniforms window.innerWidth. For the renderer to render correctly it needs to have the same dimensions as the render canvas, for the renderer (for example):
renderer.setSize(500, window.innerHeight);
Make sure you also give the uniforms the right dimensions.
How would I modify this code to produce the inside of a cube with viewer seeing the inside corner. I have added the render function.
//cube vertices
function quad(a, b, c, d)
{
var vertices = [
vec4( -0.5, -0.5, 0.5, 1.0 ),
vec4( -0.5, 0.5, 0.5, 1.0 ),
vec4( 0.5, 0.5, 0.5, 1.0 ),
vec4( 0.5, -0.5, 0.5, 1.0 ),
vec4( -0.5, -0.5, -0.5, 1.0 ),
vec4( -0.5, 0.5, -0.5, 1.0 ),
vec4( 0.5, 0.5, -0.5, 1.0 ),
vec4( 0.5, -0.5, -0.5, 1.0 )
];
var vertexColors = [
[ 0.0, 0.0, 0.0, 1.0 ], // black
[ 1.0, 0.0, 0.0, 1.0 ], // red
[ 1.0, 1.0, 0.0, 1.0 ], // yellow
[ 0.0, 1.0, 0.0, 1.0 ], // green
[ 0.0, 0.0, 1.0, 1.0 ], // blue
[ 1.0, 0.0, 1.0, 1.0 ], // magenta
[ 0.0, 1.0, 1.0, 1.0 ], // cyan
[ 1.0, 1.0, 1.0, 1.0 ] // white
];
If you want to see the "inner" of a geometry, then I recommend to activate face culling. But instead of backfaces culling, use frontface culling.
See also WebGLFundamentals - Orthographic 3D.
gl.enable( gl.CULL_FACE );
gl.cullFace( gl.BACK );
Note, to make this work correctly, all the faces of the geometry have to be oriented either clockwise or counterclockwise and the orientation must be specified by either
gl.frontFace( gl.CCW ); // counterclockwise
or
gl.frontFace( gl.CW ); // clockwise
gl.CCW is default.
See the example, where the left side is drawn with backface culling and the right side with frontface culling:
(function loadscene() {
var resize, gl, progDraw, vp_size;
var bufCube = {};
function render(delteMS){
Camera.create();
Camera.vp = vp_size;
gl.disable( gl.SCISSOR_TEST );
gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
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 );
// draw scene
gl.enable( gl.DEPTH_TEST );
gl.enable(gl.CULL_FACE);
gl.frontFace(gl.CCW);
//gl.frontFace(gl.CW);
gl.enable( gl.SCISSOR_TEST );
gl.scissor(0, 0, vp_size[0]/2, vp_size[1]);
gl.cullFace(gl.BACK);
VertexBuffer.Draw( bufCube );
gl.scissor(vp_size[0]/2, 0, vp_size[0]/2, vp_size[1]);
gl.cullFace(gl.FRONT);
VertexBuffer.Draw( bufCube );
requestAnimationFrame(render);
}
function resize() {
//vp_size = [gl.drawingBufferWidth, gl.drawingBufferHeight];
//vp_size = [gl.canvas.clientWidth, gl.canvas.clientHeight]
vp_size = [window.innerWidth, window.innerHeight]
canvas.width = vp_size[0];
canvas.height = vp_size[1];
gl.viewport( 0, 0, vp_size[0], vp_size[1] );
}
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.inCol = gl.getAttribLocation( progDraw.prog, "inCol" );
// 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 : cubeColData, attrSize : 3, attrLoc : progDraw.inCol } ],
cubeInxData );
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();
})();
<script id="draw-shader-vs" type="x-shader/x-vertex">
precision mediump float;
attribute vec3 inPos;
attribute vec3 inCol;
varying vec3 vertCol;
uniform mat4 u_projectionMat44;
uniform mat4 u_viewMat44;
uniform mat4 u_modelMat44;
void main()
{
vertCol = inCol;
vec4 modelPos = u_modelMat44 * vec4( inPos, 1.0 );
vec4 viewPos = u_viewMat44 * modelPos;
gl_Position = u_projectionMat44 * viewPos;
}
</script>
<script id="draw-shader-fs" type="x-shader/x-fragment">
precision mediump float;
varying vec3 vertCol;
void main()
{
gl_FragColor = vec4( vertCol.rgb, 1.0 );
}
</script>
<canvas id="canvas" style="border: none;"></canvas>
I am looking for a GLSL billboard vertex shader solution. I am rendering a quad with a texture on it. I currently have a vertex shader that looks like:
precision mediump float;
attribute vec3 position;
attribute vec2 uvs;
uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
varying vec2 uv;
void main() {
uv = uvs;
gl_Position = projection * view * model * vec4(position, 1);
}
My model matrix is constructed elsewhere from a rotation, translation, and scale transformation matrices.
A few of the solutions I have tried, have worked and provided a billboard (face the camera) effect. Unfortunately, they discard the original rotation, and scaling transformations to the original model matrix. The closest solution I have tried is from : http://www.geeks3d.com/20140807/billboarding-vertex-shader-glsl/
UPDATE:
Here is a MVCE of the current setup
http://requirebin.com/?gist=9491aa294f11b31af639910cfeff7140
There is a camera rotating a quad. The quad has scaling and rotation applied to it. The texture says 'player name'. The quad should always face the camera as a billboard saying 'player name' without discarding the scale, or x rotation.
To orientate the object to the viewport, you have to omit the orientation of the view matrix. The orientation is the normalized upper left 3*3 of the matrix. Since your model is flipped, you have to compensate this by inverting the X-axis:
precision mediump float;
attribute vec3 position;
attribute vec2 uvs;
uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
varying vec2 uv;
void main() {
uv = uvs;
mat4 bbView = mat4(
vec4(-1.0,0.0,0.0,0.0),
vec4(0.0,1.0,0.0,0.0),
vec4(0.0,0.0,1.0,0.0),
view[3] );
gl_Position = projection * bbView * model * vec4(position, 1);
}
Note, a transformation matrix (model and view matrix) looks like this:
( X-axis.x, X-axis.y, X-axis.z, 0 )
( Y-axis.x, Y-axis.y, Y-axis.z, 0 )
( Z-axis.x, Z-axis.y, Z-axis.z, 0 )
( trans.x, trans.y, trans.z, 1 )
The billboard matrix bbView uses the view (camera) position, but it omits the rotation provided by the view matrix. This causes the object to look like it is being viewed from the front.
At perspective projection the size of an object changes linearly with the distance to the camera.
This means if you want that the object keeps its size and its place on the viewport, then you have to scale the object by the distance to the camera:
precision mediump float;
attribute vec3 position;
attribute vec2 uvs;
uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
varying vec2 uv;
void main() {
uv = uvs;
float scale = length(view[3].xyz);
mat4 scaleMat = mat4(
vec4(scale,0.0,0.0,0.0),
vec4(0.0,scale,0.0,0.0),
vec4(0.0,0.0,1.0,0.0),
vec4(0.0,0.0,0.0,1.0) );
mat4 bbView = mat4(
vec4(-1.0,0.0,0.0,0.0),
vec4(0.0,1.0,0.0,0.0),
vec4(0.0,0.0,1.0,0.0),
view[3] );
gl_Position = projection * bbView * model * scaleMat * vec4(position, 1);
}
See the code snippet:
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.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 = "";
var node = shaderScript.firstChild;
while (node) {
if (node.nodeType == 3) source += node.textContent;
node = node.nextSibling;
}
}
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 : 0;
}
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 : 0;
}
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 );
}
function drawScene(){
var canvas = document.getElementById( "billboard-canvas" );
Camera.create();
Camera.vp = [canvas.width, canvas.height];
var currentTime = Date.now();
var deltaMS = currentTime - startTime;
var texUnit = 0;
gl.activeTexture( gl.TEXTURE0 + texUnit );
gl.bindTexture( gl.TEXTURE_2D, textureObj );
gl.viewport( 0, 0, canvas.width, canvas.height );
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 );
var rotMat = IdentityMat44();
rotMat = RotateAxis( rotMat, CalcAng( currentTime, 13.0 ), 0 );
rotMat = RotateAxis( rotMat, CalcAng( currentTime, 17.0 ), 1 );
var d = 1.0 + 0.7*Math.sin( CalcAng( currentTime, 25.0 ) )
Camera.pos = [d*rotMat[0], d*rotMat[1], d*rotMat[2]];
var viewMat = Camera.LookAt();
// set up draw shader
ShaderProgram.Use( progDraw );
ShaderProgram.SetUniformM44( progDraw, "u_projectionMat44", Camera.Perspective() );
ShaderProgram.SetUniformM44( progDraw, "u_viewMat44", viewMat );
ShaderProgram.SetUniformI1( progDraw, "u_texture", texUnit );
var modelMat = IdentityMat44();
modelMat[0] = 0.5; modelMat[5] = 0.5;
modelMat[12] = -0.55;
ShaderProgram.SetUniformM44( progDraw, "u_modelMat44", modelMat );
ShaderProgram.SetUniformF1( progDraw, "u_billboard", 0.0 );
VertexBuffer.Draw( bufPlane );
modelMat[12] = 0.55
ShaderProgram.SetUniformM44( progDraw, "u_modelMat44", modelMat );
ShaderProgram.SetUniformF1( progDraw, "u_billboard", 1.0 );
VertexBuffer.Draw( bufPlane );
}
var Texture = {};
Texture.HandleLoadedTexture2D = function( image, texture, flipY ) {
gl.activeTexture( gl.TEXTURE0 );
gl.bindTexture( gl.TEXTURE_2D, texture );
gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image );
if ( flipY != undefined && flipY == true )
gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, true );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT );
gl.bindTexture( gl.TEXTURE_2D, null );
return texture;
}
Texture.LoadTexture2D = function( name ) {
var texture = gl.createTexture();
texture.image = new Image();
texture.image.setAttribute('crossorigin', 'anonymous');
texture.image.onload = function () {
Texture.HandleLoadedTexture2D( texture.image, texture, true )
}
texture.image.src = name;
return texture;
}
var startTime;
function Fract( val ) {
return val - Math.trunc( val );
}
function CalcAng( currentTime, intervall ) {
return Fract( (currentTime - startTime) / (1000*intervall) ) * 2.0 * Math.PI;
}
function CalcMove( currentTime, intervall, range ) {
var pos = self.Fract( (currentTime - startTime) / (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 ];
}
var sliderScale = 100.0
var gl;
var progDraw;
var bufCube = {};
function sceneStart() {
var canvas = document.getElementById( "billboard-canvas");
var vp = [canvas.width, canvas.height];
gl = canvas.getContext( "experimental-webgl" );
if ( !gl )
return;
progDraw = ShaderProgram.Create(
[ { source : "draw-shader-vs", stage : gl.VERTEX_SHADER },
{ source : "draw-shader-fs", stage : gl.FRAGMENT_SHADER }
] );
progDraw.inPos = gl.getAttribLocation( progDraw, "inPos" );
progDraw.inTex = gl.getAttribLocation( progDraw, "inTex" );
if ( progDraw == 0 )
return;
var planPosData = [-1.0, -1.0, 0.0, 1.0, -1.0, 0.0, 1.0, 1.0, 0.0, -1.0, 1.0, 0.0];
var planTexData = [ 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0 ];
var planInxData = [0,1,2,0,2,3];
bufPlane = VertexBuffer.Create(
[ { data : planPosData, attrSize : 3, attrLoc : progDraw.inPos },
{ data : planTexData, attrSize : 2, attrLoc : progDraw.inTex } ],
planInxData );
textureObj = Texture.LoadTexture2D( "https://raw.githubusercontent.com/Rabbid76/graphics-snippets/master/resource/texture/tree.jpg" );
startTime = Date.now();
setInterval(drawScene, 50);
}
<script id="draw-shader-vs" type="x-shader/x-vertex">
precision mediump float;
attribute vec3 inPos;
attribute vec2 inTex;
varying vec2 vertTex;
uniform mat4 u_projectionMat44;
uniform mat4 u_viewMat44;
uniform mat4 u_modelMat44;
uniform float u_billboard;
void main()
{
vertTex = inTex;
float scale = u_billboard > 0.5 ? length(u_viewMat44[3].xyz) : 1.0;
mat4 scaleMat = mat4(
vec4(scale,0.0,0.0,0.0),
vec4(0.0,scale,0.0,0.0),
vec4(0.0,0.0,1.0,0.0),
vec4(0.0,0.0,0.0,1.0) );
mat4 bbView = mat4(
vec4(1.0,0.0,0.0,0.0),
vec4(0.0,1.0,0.0,0.0),
vec4(0.0,0.0,1.0,0.0),
u_viewMat44[3] );
mat4 view = u_billboard > 0.5 ? bbView : u_viewMat44;
gl_Position = u_projectionMat44 * view * scaleMat * u_modelMat44 * vec4( inPos, 1.0 );
}
</script>
<script id="draw-shader-fs" type="x-shader/x-fragment">
precision mediump float;
varying vec2 vertTex;
uniform sampler2D u_texture;
uniform sampler2D u_normalMap;
void main()
{
vec3 texColor = texture2D( u_texture, vertTex.st ).rgb;
gl_FragColor = vec4( texColor.rgb, 1.0 );
}
</script>
<body onload="sceneStart();">
<canvas id="billboard-canvas" style="border: none;" width="512" height="512"></canvas>
</body>
I'm rendering circles using regl, and have three goals:
The canvas should be transparent, showing HTML content behind it.
Circles should be antialiased smoothly.
Overlapping circles should look reasonable (blend colors, no corners showing)
So far, I have this: Glitch code and demo.
UPDATE: The demo links now reflect the working, accepted answer. Code below is unchanged.
index.js
const regl = require('regl');
const glsl = require('glslify');
const vertexShader = glsl.file('../shaders/vertex.glsl');
const fragmentShader = glsl.file('../shaders/fragment.glsl');
// Create webgl context and clear.
const canvasEl = document.querySelector('canvas');
const app = regl({
canvas: canvasEl,
extensions: ['OES_standard_derivatives']
});
app.clear({color: [0, 0, 0, 0], depth: 1});
// Generate random points and colors.
const attributes = {position: [], color: []};
for (let i = 0; i < 100; i++) {
attributes.position.push(Math.random() * 2 - 1, Math.random() * 2 - 1);
attributes.color.push(Math.random(), Math.random(), Math.random());
}
// Define draw instructions.
const draw = app({
vert: vertexShader,
frag: fragmentShader,
attributes: attributes,
count: 100,
primitive: 'points',
depth: {enable: true},
blend: {
enable: true
}
});
// Draw the points.
draw();
vertex.glsl
// vertex.glsl
precision mediump float;
attribute vec2 position;
attribute vec3 color;
varying vec3 vColor;
void main() {
vColor = color;
gl_Position = vec4(position, 0, 1);
gl_PointSize = 40.;
}
fragment.glsl
// fragment.glsl
#ifdef GL_OES_standard_derivatives
#extension GL_OES_standard_derivatives : enable
#endif
precision mediump float;
varying vec3 vColor;
void main() {
float r = 0.0, delta = 0.0, alpha = 1.0;
vec2 cxy = 2.0 * gl_PointCoord - 1.0;
r = dot(cxy, cxy);
#ifdef GL_OES_standard_derivatives
delta = fwidth(r);
alpha = 1.0 - smoothstep(1.0 - delta, 1.0 + delta, r);
#endif
gl_FragColor = vec4(vColor, alpha);
}
However, the result looks not-so-great. Corners are visible, and circles aren't blending properly.
I've also tried adding the following blend terms:
func: {
srcRGB: 'src alpha',
srcAlpha: 'one minus src alpha',
dstRGB: 'one minus src alpha',
dstAlpha: 'src alpha'
}
This looks a bit better, but the corners are still there and something is wrong when the background is white.
Could you suggest improvements to this? (And maybe point me to better information about blending, if that's what I'm missing here) Thanks!
You should set up your blending parameters like this:
func: {
srcRGB: 'src alpha',
srcAlpha: 'src alpha',
dstRGB: 'one minus src alpha',
dstAlpha: 'one minus src alpha'
}
This means that your destination and source color become blended like this:
Red, green and blue (srcRGB: 'src alpha', dstRGB: 'one minus src alpha'):
R_dest = R_dest * (1 - Alpha_src) + R_src * Alpha_src
G_dest = G_dest * (1 - Alpha_src) + G_src * Alpha_src
B_dest = R_dest * (1 - Alpha_src) + R_src * Alpha_src
The alpha channel (srcAlpha: 'src alpha', dstAlpha: 'one minus src alpha'):
Alpha_dest = Alpha_dest * (1 - Alpha_src) + Alpha_src * Alpha_src
seel also glBlendFunc and glBlendFuncSeparate
Further you have to make sure that the depth test is disabled
See the WebGL example above (tested with Firefox, Chrome, Edge, Opera):
<script type="text/javascript">
back_vert =
"precision mediump float; \n" +
"attribute vec2 inPos; \n" +
"varying vec2 pos; \n" +
"uniform mat4 u_projectionMat44;" +
"uniform mat4 u_modelViewMat44;" +
"void main()" +
"{" +
" pos = inPos.xy;" +
" vec4 viewPos = u_modelViewMat44 * vec4( inPos.xy, 0.0, 1.0 );" +
" gl_Position = u_projectionMat44 * viewPos;" +
"}";
back_frag =
"precision mediump float; \n" +
"varying vec2 pos; \n" +
"void main() \n" +
"{ \n" +
" vec2 coord = pos * 0.5 + 0.5; \n" +
" float gray = smoothstep( 0.3, 0.7, (coord.x + coord.y) * 0.5 ); \n" +
" gl_FragColor = vec4( vec3( gray ), 1.0 ); \n" +
"}";
draw_vert =
"precision mediump float; \n" +
"attribute vec2 inPos; \n" +
"varying vec2 pos; \n" +
"uniform mat4 u_projectionMat44;" +
"uniform mat4 u_modelViewMat44;" +
"void main()" +
"{" +
" pos = inPos.xy;" +
" vec4 viewPos = u_modelViewMat44 * vec4( inPos.xy, 0.0, 1.0 );" +
" gl_Position = u_projectionMat44 * viewPos;" +
"}";
draw_frag =
"precision mediump float; \n" +
"varying vec2 pos; \n" +
"uniform vec4 u_color;" +
"uniform vec2 u_vp;" +
"void main()" +
"{" +
" float r = length( pos );" +
" float d = 4.0 * length( 1.0 / u_vp ); \n" +
" float a = 1.0 - smoothstep( 1.0 - d, 1.0 + d, r ); \n" +
" gl_FragColor = vec4( u_color.rgb, u_color.a * a );" +
"}";
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 Translate( matA, trans ) {
var matB = new glArrayType(16);
for ( var i = 0; i < 16; ++ i ) matB[i] = matA[i];
for ( var i = 0; i < 3; ++ i )
matB[12+i] = matA[i] * trans[0] + matA[4+i] * trans[1] + matA[8+i] * trans[2] + matA[12+i];
return matB;
}
function Scale( matA, scale ) {
var matB = new glArrayType(16);
for ( var i = 0; i < 16; ++ i ) matB[i] = matA[i];
for ( var a = 0; a < 4; ++ a )
for ( var i = 0; i < 3; ++ i )
matB[a*4+i] = matA[a*4+i] * scale[0];
return matB;
}
Ortho = function( l, r, t, b, n, f ) {
var fn = f + n;
var f_n = f - n;
var m = IdentityMat44();
m[0] = 2/(r-l); m[1] = 0; m[2] = 0; m[3] = 0;
m[4] = 0; m[5] = 2/(t-b); m[6] = 0; m[7] = 0;
m[8] = 0; m[9] = 0; m[10] = -2 / f_n; m[11] = -fn / f_n;
m[12] = 0; m[13] = 0; m[14] = 0; m[15] = 1;
return m;
}
vec4_add = function( a, b ) { return [ a[0]+b[0], a[1]+b[1], a[2]+b[2], a[3]+b[3] ]; }
vec4_sub = function( a, b ) { return [ a[0]-b[0], a[1]-b[1], a[2]-b[2], a[3]-b[3] ]; }
vec4_mul = function( a, b ) { return [ a[0]*b[0], a[1]*b[1], a[2]*b[2], a[3]*b[3] ]; }
vec4_scale = function( a, s ) { return [ a[0]*s, a[1]*s, a[2]*s, a[3]*s ]; }
// shader program object
var ShaderProgram = {};
ShaderProgram.Create = function( shaderList, uniformNames ) {
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.unifomLocation = {};
for ( var i_n = 0; i_n < uniformNames.length; ++ i_n ) {
var name = uniformNames[i_n];
progObj.unifomLocation[name] = gl.getUniformLocation( progObj, name );
}
}
return progObj;
}
ShaderProgram.Use = function( progObj ) { gl.useProgram( progObj ); }
ShaderProgram.SetUniformInt = function( progObj, name, val ) { gl.uniform1i( progObj.unifomLocation[name], val ); }
ShaderProgram.SetUniform2f = function( progObj, name, arr ) { gl.uniform2fv( progObj.unifomLocation[name], arr ); }
ShaderProgram.SetUniform3f = function( progObj, name, arr ) { gl.uniform3fv( progObj.unifomLocation[name], arr ); }
ShaderProgram.SetUniform4f = function( progObj, name, arr ) { gl.uniform4fv( progObj.unifomLocation[name], arr ); }
ShaderProgram.SetUniformMat44 = function( progObj, name, mat ) { gl.uniformMatrix4fv( progObj.unifomLocation[name], false, mat ); }
ShaderProgram.CompileShader = function( source, shaderStage ) {
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 : 0;
}
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 : 0;
}
function drawScene(){
var canvas = document.getElementById( "camera-canvas" );
var vp = [canvas.width, canvas.height];
var currentTime = Date.now();
var deltaMS = currentTime - startTime;
var aspect = canvas.width / canvas.height;
var matOrtho = Ortho( -aspect, aspect, 1, -1, -1, 1 );
var alpha = document.getElementById( "alpha" ).value / 100;
gl.viewport( 0, 0, canvas.width, canvas.height );
gl.disable( gl.DEPTH_TEST );
gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
gl.disable( gl.BLEND );
ShaderProgram.Use( progBack );
ShaderProgram.SetUniformMat44( progBack, "u_projectionMat44", matOrtho );
ShaderProgram.SetUniformMat44( progBack, "u_modelViewMat44", IdentityMat44() );
gl.enableVertexAttribArray( progBack.inPos );
gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.pos );
gl.vertexAttribPointer( progBack.inPos, 2, gl.FLOAT, false, 0, 0 );
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufObj.inx );
gl.drawElements( gl.TRIANGLES, bufObj.inx.len, gl.UNSIGNED_SHORT, 0 );
gl.disableVertexAttribArray( progBack.pos );
gl.enable( gl.BLEND );
gl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA );
ShaderProgram.Use( progDraw );
gl.enableVertexAttribArray( progDraw.inPos );
gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.pos );
gl.vertexAttribPointer( progDraw.inPos, 2, gl.FLOAT, false, 0, 0 );
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufObj.inx );
ShaderProgram.SetUniformMat44( progDraw, "u_projectionMat44", matOrtho );
ShaderProgram.SetUniform2f( progDraw, "u_vp", vp );
var col = [ [1.0,0.0,0.0], [1.0,1.0,0.0], [0.0,0.0,1.0] ];
var time = [ 7.0, 11.0, 13.0 ];
for ( var i = 0; i < 3; ++ i ) {
var modelMat = Scale( IdentityMat44(), [ 0.3, 0.3, 0.3] );
var angRad = CalcAng( currentTime, time[i] ) + i * Math.PI * 2 / 3;
var sinAng = Math.sin(angRad), cosAng = Math.cos(angRad);
modelMat[12] = cosAng * 0.3 + i * 0.2;
modelMat[13] = sinAng * 0.3 + i * 0.2;
ShaderProgram.SetUniformMat44( progDraw, "u_modelViewMat44", modelMat );
var color = col[i];
color.push( alpha );
ShaderProgram.SetUniform4f( progDraw, "u_color", color );
gl.drawElements( gl.TRIANGLES, bufObj.inx.len, gl.UNSIGNED_SHORT, 0 );
}
gl.disableVertexAttribArray( progDraw.pos );
}
var startTime;
function Fract( val ) {
return val - Math.trunc( val );
}
function CalcAng( currentTime, intervall ) {
return Fract( (currentTime - startTime) / (1000*intervall) ) * 2.0 * Math.PI;
}
function CalcMove( currentTime, intervall, range ) {
var pos = self.Fract( (currentTime - startTime) / (1000*intervall) ) * 2.0
var pos = pos < 1.0 ? pos : (2.0-pos)
return range[0] + (range[1] - range[0]) * pos;
}
var mousePos = [-1, -1];
var gl;
var prog;
var bufObj = {};
function cameraStart() {
var canvas = document.getElementById( "camera-canvas");
gl = canvas.getContext( "experimental-webgl" );
if ( !gl )
return;
var vp = [canvas.width, canvas.height];
progBack = ShaderProgram.Create(
[ { source : back_vert, stage : gl.VERTEX_SHADER },
{ source : back_frag, stage : gl.FRAGMENT_SHADER }
],
[ "u_projectionMat44", "u_modelViewMat44"] );
progBack.inPos = gl.getAttribLocation( progBack, "inPos" );
if ( progBack == 0 )
return;
progDraw = ShaderProgram.Create(
[ { source : draw_vert, stage : gl.VERTEX_SHADER },
{ source : draw_frag, stage : gl.FRAGMENT_SHADER }
],
[ "u_projectionMat44", "u_modelViewMat44", "u_color", "u_alpha", "u_vp"] );
progDraw.inPos = gl.getAttribLocation( progDraw, "inPos" );
if ( progDraw == 0 )
return;
var pos = [ -1, -1, 1, -1, 1, 1, -1, 1 ];
var inx = [ 0, 1, 2, 0, 2, 3 ];
bufObj.pos = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.pos );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( pos ), gl.STATIC_DRAW );
bufObj.inx = gl.createBuffer();
bufObj.inx.len = inx.length;
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufObj.inx );
gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( inx ), gl.STATIC_DRAW );
startTime = Date.now();
setInterval(drawScene, 50);
}
</script>
<body onload="cameraStart();">
<div style="margin-left: 260px;">
<div style="float: right; width: 100%; background-color: #CCF;">
<form name="inputs">
<table>
<tr> <td> alpha </td>
<td> <input type="range" id="alpha" min="0" max="100" value="50"/></td> </tr>
</table>
</form>
</div>
<div style="float: right; width: 260px; margin-left: -260px;">
<canvas id="camera-canvas" style="border: none;" width="256" height="256"></canvas>
</div>
<div style="clear: both;"></div>
</div>
</body>
Canvas requires pre-multiplied alpha unless you specifically request otherwise so problem #1 is your blend function should be
blend: {
enable: true,
func: {
srcRGB: 'one',
srcAlpha: 'one',
dstRGB: 'one minus src alpha',
dstAlpha: 'one minus src alpha',
},
},
You also need to return premultiplied values from your shader
gl_FragColor = vec4(vColor, alpha);
gl_FragColor.rgb *= gl_FragColor.a; // premultiply by alpha
}
The other problem is you have the depth test on. The default depth function is LESS. That means new pixels will not be drawn unless their Z value is LESS than existing Z values. Since all of your circles are drawn at the same depth no new circle will get drawn anywhere a previous circle was drawn.
The easiest fix is to turn off the depth test
depth: {enable: false},
Result:
As for why premultiplied alpha see
https://developer.nvidia.com/content/alpha-blending-pre-or-not-pre
Im newbie at webgl world and trying to learn something. while after i figured out how shaders work, wanted to go a step further and make a smooth, transparent cloud. Actually i made it, but as you see in the images below, there is some overlaping edges look jagged.
can you explain why this is happening and how to solve it?
thank you
v77
Snippet
var camera, controls, scene, renderer;
var sceneCss, rendererCss;
var dirLight, ambientLight, pointLight;
var displacementMaterial;
var wH = window.innerHeight;
var wW = window.innerWidth;
var start = Date.now();
var properties;
var cloudRadius = 150;
var cloudMesh;
var noiseMaterial, noiseMap;
var uniforms, displamentUniforms;
$(document).ready(function() {
//init Core Engine;
init();
//init All Engines;
onLoaded();
animateAll();
});
//Give it life;
function init() {
properties = {
smoke: 2.0,
heat: 0.0007,
shapeBiasX: 1.5,
shapeBiasY: 2.5,
displacementScale: 40,
displacementBias: -22,
turbulence: 40,
twist: 0,
wireframes: false,
rotationX: .5,
rotationY: 0,
rotationZ: 0,
opacity: 1.0
}
// add camera and controls
camera = new THREE.PerspectiveCamera(70, wW / wH, 0.1, 20000);
camera.position.set(0, 0, 400);
//PostProcess Materials
sceneRenderTarget = new THREE.Scene();
cameraOrtho = new THREE.OrthographicCamera(wW / -2, wW / 2, wH / 2, wH / -2, -10000, 10000);
cameraOrtho.position.z = 100;
cameraOrtho.updateProjectionMatrix();
var plane = new THREE.PlaneGeometry(wW, wH);
quadTarget = new THREE.Mesh(plane, new THREE.MeshBasicMaterial({ transparent: true, opacity: .1, color: 0x000000 }));
quadTarget.position.z = -500;
sceneRenderTarget.add(quadTarget);
//
//scene
scene = new THREE.Scene();
sceneCss = new THREE.Scene();
fog = new THREE.FogExp2(0x212121, 0.002);
scene.fog = fog;
//renderer
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(wW, wH);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.sortObjects = false;
renderer.domElement.style.zIndex = 0;
renderer.setClearColor(0x222222);
document.body.appendChild(renderer.domElement);
}
function onLoaded() {
//Will Check Processes
createClouds();
}
function createClouds() {
uniforms = {
time: {
type: "f",
value: 1.0
},
uSpeed: {
type: "f",
value: 1.0
},
scale: {
type: "v2",
value: new THREE.Vector2(1, 1)
},
opacity: {
type: "f",
value: 1.0
}
};
noiseMaterial = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: document.getElementById('noise_vertex').textContent,
fragmentShader: document.getElementById('noise_fragment').textContent,
lights: false,
wireframe: properties.wireframes
});
noiseMap = new THREE.WebGLRenderTarget(512, 512, {
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
format: THREE.RGBFormat,
wrapS: THREE.RepeatWrapping
});
displacementUniforms = {
time: {
type: "f",
value: 1.0
},
tHeightMap: {
type: "t",
value: noiseMap.texture
},
uDisplacementBias: {
type: "f",
value: properties.displacementBias
},
uDisplacementScale: {
type: "f",
value: properties.displacementScale
},
uColor1: {
type: "c",
value: new THREE.Color(0xffff00)
},
uColor2: {
type: "c",
value: new THREE.Color(0x0000ff)
},
uSmoke: {
type: "f",
value: properties.smoke
},
uShapeBias: {
type: "v2",
value: new THREE.Vector2(properties.shapeBiasX, properties.shapeBiasY)
},
uScreenHeight: {
type: "f",
value: wH
},
uTurbulence: {
type: "f",
value: properties.turbulence
},
uTwist: {
type: "f",
value: properties.twist
},
opacity: {
type: "f",
value: 1.0
}
};
displacementMaterial = new THREE.ShaderMaterial({
wireframe: properties.wireframes,
transparent: true,
uniforms: displacementUniforms,
vertexShader: document.getElementById('displacement_vertex').textContent,
fragmentShader: document.getElementById('displacement_fragment').textContent,
premultipliedAlpha: true,
side: THREE.DoubleSide,
shading: THREE.SmoothShading,
depthTest: false
});
var geometrySphere = new THREE.SphereGeometry(cloudRadius, 140, 100);
geometrySphere.computeFaceNormals();
cloudMesh = new THREE.Mesh(geometrySphere, displacementMaterial);
cloudMesh.position.y = -40;
cloudMesh.renderOrder = 5;
scene.add(cloudMesh);
}
function animateAll() {
uniforms.uSpeed.value += properties.heat;
uniforms.time.value += properties.heat * .3;
displacementUniforms.opacity.value = properties.opacity;
displacementMaterial.uniforms["time"].value += properties.heat * .3;
//cloudMesh.rotation.x = properties.rotationX;
cloudMesh.rotation.z = properties.rotationZ;
//cloudMesh.rotation.y = properties.rotationY;
cloudMesh.rotation.y += 0.002;
requestAnimationFrame(animateAll);
renderAll();
}
//render
function renderAll() {
renderer.clear();
quadTarget.material = noiseMaterial;
renderer.render(sceneRenderTarget, cameraOrtho, noiseMap, true);
renderer.render(scene, camera);
}
canvas {
width: 100%;
height: 100%;
position: absolute;
z-index: -10
}
body {
overflow: hidden;
padding: 0;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r77/three.min.js"></script>
<script src="https://code.jquery.com/jquery-1.12.2.js"></script>
<script data-src="shaders/displacement_vertex.js" data-name="Displacement" type="x-shader/x-vertex" id="displacement_vertex">
uniform float time;
uniform vec2 scale;
uniform float uTwist;
varying vec2 vUv;
varying vec3 vNormal;
uniform vec2 uShapeBias;
uniform float uTurbulence;
#ifdef VERTEX_TEXTURES
uniform sampler2D tHeightMap;
uniform float uDisplacementScale;
uniform float uDisplacementBias;
#endif
vec4 DoTwist( vec4 pos, float t )
{
float st = sin(t);
float ct = cos(t);
vec4 new_pos;
new_pos.x = pos.x*ct - pos.z*st;
new_pos.z = pos.x*st + pos.z*ct;
new_pos.y = pos.y;
new_pos.w = pos.w;
return( new_pos );
}
void main( void ) {
vUv = uv;
vNormal = normalize( normalMatrix * normal );
//change matrix
vec4 mPosition = modelMatrix * vec4( position, 1.0 );
mPosition.x *= uShapeBias.x +1.0; // uShapeBias.x*(vUv.x+1.0);
mPosition.y *= (1.0 -(vUv.y-0.5)*-uShapeBias.y);
//mPosition.y -= 40.0;
float turbFactor = uTurbulence*(vUv.y-0.5);
//shape turbulance
mPosition.x += sin(mPosition.y/100.0 + time*20.0 )*turbFactor;
mPosition.z += cos(mPosition.y/100.0 + time*20.0 )*turbFactor;
//twist
float angle_rad = uTwist * 3.14159 / 180.0;
float height = -300.0;
float ang = (position.y-height*0.5)/height * angle_rad;
vec4 twistedPosition = DoTwist(mPosition, ang);
vec4 twistedNormal = DoTwist(vec4(vNormal,1.0), ang);
//change matrix
vec4 mvPosition = viewMatrix * twistedPosition;
#ifdef VERTEX_TEXTURES
vec3 dv = texture2D( tHeightMap, vUv ).xyz;
float df = uDisplacementScale * dv.x + uDisplacementBias;
vec4 displacedPosition = vec4( twistedNormal.xyz * df, 0.0 ) + mvPosition;
gl_Position = projectionMatrix * displacedPosition;
#else
gl_Position = projectionMatrix * mvPosition;
#endif
}
</script>
<script data-src="shaders/displacement_fragment.js" data-name="Displacement" type="x-shader/x-fragment" id="displacement_fragment">
varying vec2 vUv;
uniform sampler2D tHeightMap;
uniform float uSmoke;
uniform vec3 uColor1;
uniform vec3 uColor2;
uniform float uScreenHeight;
void main( void ) {
vec4 heightColor = texture2D( tHeightMap, vUv);
vec3 heightAlpha = texture2D( tHeightMap, vUv).xyz;
vec3 gradient1 = uColor1/(gl_FragCoord.y/uScreenHeight*4.0);
vec3 gradient2 = uColor2/(gl_FragCoord.y/uScreenHeight*4.0);
vec3 fireSumColor = (gradient1+gradient2)*heightColor.b;
float opacity = heightAlpha.x *.05;
//smoke
gl_FragColor = vec4(mix( fireSumColor, vec3(1.0), gl_FragCoord.y/uScreenHeight*uSmoke ), opacity);
float depth = gl_FragCoord.z / gl_FragCoord.w;
float fogFactor = smoothstep( 10.0, 400.0, depth* .9 );
gl_FragColor = mix( gl_FragColor, vec4( vec3(0.0,0.0,0.0), gl_FragColor.w ), fogFactor )*1.0;
// gl_FragColor = gl_FragColor*vec4(vec3(1.0), 1.0);
}
</script>
<script data-src="shaders/noise_vertex.js" data-name="Noise" type="x-shader/x-vertex" id="noise_vertex">
varying vec2 vUv;
uniform vec2 scale;
void main( void ) {
vUv = uv * scale;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
</script>
<script data-src="shaders/noise_fragment.js" data-name="Noise" type="x-shader/x-fragment" id="noise_fragment">
//
// Description : Array and textureless GLSL 3D simplex noise function.
// Author : Ian McEwan, Ashima Arts.
// Maintainer : ijm
// Lastmod : 20110409 (stegu)
// License : Copyright (C) 2011 Ashima Arts. All rights reserved.
// Distributed under the MIT License. See LICENSE file.
//
uniform float time;
uniform float uSpeed;
varying vec2 vUv;
vec4 permute( vec4 x ) {
return mod( ( ( x * 34.0 ) + 1.0 ) * x, 289.0 );
}
vec4 taylorInvSqrt( vec4 r ) {
return 1.79284291400159 - 0.85373472095314 * r;
}
float PI = 3.14159265;
float TWOPI = 6.28318531;
float BaseRadius = 1.0;
vec3 sphere( float u, float v) {
u *= PI;
v *= TWOPI;
vec3 pSphere;
pSphere.x = BaseRadius * cos(v) * sin(u);
pSphere.y = BaseRadius * sin(v) * sin(u);
pSphere.z = BaseRadius * cos(u);
return pSphere;
}
float snoise( vec3 v ) {
const vec2 C = vec2( 1.0 / 6.0, 1.0 / 3.0 );
const vec4 D = vec4( 0.0, 0.5, 1.0, 2.0 );
// First corner
vec3 i = floor( v + dot( v, C.yyy ) );
vec3 x0 = v - i + dot( i, C.xxx );
// Other corners
vec3 g = step( x0.yzx, x0.xyz );
vec3 l = 1.0 - g;
vec3 i1 = min( g.xyz, l.zxy );
vec3 i2 = max( g.xyz, l.zxy );
vec3 x1 = x0 - i1 + 1.0 * C.xxx;
vec3 x2 = x0 - i2 + 2.0 * C.xxx;
vec3 x3 = x0 - 1. + 3.0 * C.xxx;
// Permutations
i = mod( i, 289.0 );
vec4 p = permute( permute( permute(
i.z + vec4( 0.0, i1.z, i2.z, 1.0 ) )
+ i.y + vec4( 0.0, i1.y, i2.y, 1.0 ) )
+ i.x + vec4( 0.0, i1.x, i2.x, 1.0 ) );
// Gradients
// ( N*N points uniformly over a square, mapped onto an octahedron.)
float n_ = 1.0 / 7.0; // N=7
vec3 ns = n_ * D.wyz - D.xzx;
vec4 j = p - 49.0 * floor( p * ns.z *ns.z ); // mod(p,N*N)
vec4 x_ = floor( j * ns.z );
vec4 y_ = floor( j - 7.0 * x_ ); // mod(j,N)
vec4 x = x_ *ns.x + ns.yyyy;
vec4 y = y_ *ns.x + ns.yyyy;
vec4 h = 1.0 - abs( x ) - abs( y );
vec4 b0 = vec4( x.xy, y.xy );
vec4 b1 = vec4( x.zw, y.zw );
vec4 s0 = floor( b0 ) * 2.0 + 1.0;
vec4 s1 = floor( b1 ) * 2.0 + 1.0;
vec4 sh = -step( h, vec4( 0.0 ) );
vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
vec3 p0 = vec3( a0.xy, h.x );
vec3 p1 = vec3( a0.zw, h.y );
vec3 p2 = vec3( a1.xy, h.z );
vec3 p3 = vec3( a1.zw, h.w );
// Normalise gradients
vec4 norm = taylorInvSqrt( vec4( dot( p0, p0 ), dot( p1, p1 ), dot( p2, p2 ), dot( p3, p3 ) ) );
p0 *= norm.x;
p1 *= norm.y;
p2 *= norm.z;
p3 *= norm.w;
// Mix final noise value
vec4 m = max( 0.6 - vec4( dot( x0, x0 ), dot( x1, x1 ), dot( x2, x2 ), dot( x3, x3 ) ), 0.0 );
m = m * m;
return 42.0 * dot( m*m, vec4( dot( p0, x0 ), dot( p1, x1 ), dot( p2, x2 ), dot( p3, x3 ) ) );
}
float surface( vec3 coord ) {
float n = 0.0;
n += 0.7 * abs( snoise( coord ) );
n += 0.25 * abs( snoise( coord * 2.0 ) );
n += 0.125 * abs( snoise( coord * 4.0 ) );
n += 0.0625 * abs( snoise( coord * 8.0 ) );
return n;
}
void main( void ) {
vec3 coord = sphere(vUv.y,vUv.x);
coord.x += uSpeed;
coord.y += -time;
coord.z += -time;
float n = surface( coord );
gl_FragColor = vec4( vec3( n, n, n ), 1.0 );
}
</script>
When you have overlapping transparent materials in three.js, you can often remove unwanted artifacts by setting
material.depthTest = false;
It depends on your use case.
It will not work well if you have opaque objects the occlude the transparent objects, however. But that is not your use case here.
EDIT: As #Bahadir points out in the comment below, you can an also try
material.depthWrite = false;
three.js r.77