2017-07-12 5 views
1

Ich bin Rendering Kreise regl verwenden und haben drei Ziele:Blending geglätteten Kreise mit regl

  1. Die Leinwand transparent sein sollte, HTML-Inhalte dahinter zeigt.
  2. Kreise sollten glatt antialiasiert werden.
  3. Overlapping Kreise sollten angemessen (Mischung Farben, zeigt keine Ecken) schauen

Bisher habe ich diese: Glitch code und demo.

UPDATE: Die Demo-Links spiegeln nun die funktionierende, akzeptierte Antwort wider. Der folgende Code ist unverändert.

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); 
} 

Allerdings sieht das Ergebnis nicht so toll aus. Ecken sind sichtbar und Kreise mischen sich nicht richtig.

circles with default blending

Ich habe versucht, auch folgende Mischung Bedingungen hinzugefügt werden:

func: { 
    srcRGB: 'src alpha', 
    srcAlpha: 'one minus src alpha', 
    dstRGB: 'one minus src alpha', 
    dstAlpha: 'src alpha' 
} 

circles with custom blending

Dieses etwas besser aussieht, aber die Ecken sind immer noch da und etwas falsch ist, wenn die Hintergrund ist weiß.

Können Sie Verbesserungen vorschlagen? (Und vielleicht zeigen Sie mir bessere Informationen über das Mischen, wenn das ist, was ich hier vermisse) Danke!

Antwort

1

Sie sollten Ihre Mischungsparameter wie folgt ein:

func: { 
    srcRGB: 'src alpha', 
    srcAlpha: 'src alpha', 
    dstRGB: 'one minus src alpha', 
    dstAlpha: 'one minus src alpha' 
} 

Das bedeutet, dass Ihr Ziel und Quellfarbe wie folgt gemischt werden:

Rot, Grün und Blau (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 

Der Alphakanal (srcAlpha: 'src alpha', dstAlpha: 'one minus src alpha'):

Alpha_dest = Alpha_dest * (1 - Alpha_src) + Alpha_src * Alpha_src 

seel auch glBlendFunc und glBlendFuncSeparate

Ferner müssen Sie sicherstellen, dass der Tiefentest

Siehe WebGL Beispiel oben (getestet mit Firefox, Chrome, Rand, Opera) deaktiviert ist :

<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>

+1

Danke für die detaillierte Antwort! Ich habe das akzeptiert, weil die Mischparameter am besten zu meinem Fall passen. Für zukünftige Leser ist aber auch das in [gmans Antwort] (https://stackoverflow.com/a/45074112/1314762) erwähnte Prämultiplexalpha notwendig. –

1

Leinwand erfordert premultiplied alpha, wenn Sie speziell sonst so Problem # anfordern 1 ist Ihre Mischung Funktion

blend: { 
    enable: true, 
    func: { 
     srcRGB: 'one', 
     srcAlpha: 'one', 
     dstRGB: 'one minus src alpha', 
     dstAlpha: 'one minus src alpha', 
    }, 
    }, 

Sie auch premultiplied Werte von Ihrem Shader

gl_FragColor = vec4(vColor, alpha); 
    gl_FragColor.rgb *= gl_FragColor.a; // premultiply by alpha 
} 

Die zurückgeben müssen sein sollte anderes Problem ist der Tiefen-Test. Die Standardtiefe ist LESS. Das bedeutet, dass neue Pixel erst dann gezeichnet werden, wenn ihr Z-Wert WENIGER als die vorhandenen Z-Werte ist. Da alle Kreise in der gleichen Tiefe gezeichnet werden, wird an keiner Stelle, an der ein vorheriger Kreis gezeichnet wurde, ein neuer Kreis gezeichnet.

Die einfachste Lösung ist der Tiefentest

depth: {enable: false}, 

Ergebnis zu deaktivieren:

enter image description here

Was, warum premultiplied alpha sehen

https://developer.nvidia.com/content/alpha-blending-pre-or-not-pre

+0

Upvoted, aber ich habe die andere Antwort akzeptiert; beide gaben hilfreiche Lösungen. Vielen Dank! –