11.4 - Transparency (and Alpha Blending)

The z-buffer algorithm for performing hidden surface removal stores the color of the object that is closest to the camera in the color buffer of the frame buffer. This is the desired behavior when solid objects block the view of other solid objects. But what about objects that are partially transparent that allow the objects behind them to be partially visible? This lesson explains the standard technique for rendering objects that contain transparent surfaces.

Transparency

If an object allows light to pass through it, a viewer sees some of the light reflected from the object and some of the light reflected from the object that is behind the surface. Therefore, transparency requires the combining of light from two sources. Let’s review the z-buffer algorithm – which looks like this:

void renderPixel(x, y, z, color) {
  if (z < zbuffer[x][y]) {
    zbuffer[x][y] = z;
    colorbuffer[x][y] = color;
  }
}

Notice that we have two colors represented in this algorithm: 1) the color already in the color buffer, colorbuffer[x][y], and 2) the color of the object being rendered, color. If we set up the rendering context carefully, the graphics pipeline can combine the colors using the amount of transparency of the object. The rendering algorithm changes to this:

void renderPixel(x, y, z, color) {
  if (z < zbuffer[x][y]) {
    zbuffer[x][y] = z;
    colorbuffer[x][y] = (colorbuffer[x][y] * percent_left_over) + (color * percent_of_reflection);
  }
}

Where do the “percentages” come from? Given an RGBA (red, green, blue, alpha) color value, the “alpha” value represents the amount of light that is reflected. If alpha is 1.0, all light is reflected and the object is “opaque”. If the alpha value is 0.75, the object reflects 75% of the light that strikes it, which means 25% of the light passes through. So the percentage values come from the color value like this:

percent_of_reflection = color.alpha; // or color[3]
percent_left_over     = 1.0 - percent_of_reflection;

To setup the graphics pipeline to perform this “blending of colors”, you call two JavaScript functions: the first enables color blending, and the second specifies the blending percentages using pre-defined enumerated constants. Hopefully the names of the constants are self explanatory.

gl.enable(gl.BLEND)
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

Note that transparent objects are not visible if they are behind other objects. Therefore, we don’t want to turn hidden surface removal off. However, what if there are multiple transparent objects in a scene and light travels through more than one of them? In such a situation we need to perform the color combination for each object the light passes through. To calculate the correct final color we need to process the objects furthest from the camera first, and the objects closest to the camera last! That means we need to sort the transparent objects! We’ll talk more about sorting in a few minutes. Taking all of these issues into account, here are the major steps in a rendering algorithm the handles transparent surfaces:

  1. Clear the color buffer and the z-buffer - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

  2. Render all of the objects in a scene that are opaque. (The order does not matter.)

  3. Sort the transparent objects in the scene based on their distance from the camera. (Greatest to least distance.)

  4. Sort the graphic primitives in a model based on their distance from the camera. (Greatest to least distance.)

  5. Enable blending and set the blending percentages.

    gl.enable(gl.BLEND);
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
    
  6. Keep the z-buffer algorithm active, but disable changes to the zbuffer array. (gl.depthMask(false)) (This applies only if transparent models occupy the same 3D space. This is discussed below in more detail.)

  7. Render the sorted transparent objects and primitives, greatest distance from the camera to the least distance.

What makes this algorithm very inefficient is the sorting. But before we discuss sorting, recognize that prior knowledge about a specific scene might allow you to ignore sorting altogether. Here are some specific scenarios where you can safely ignore sorting:

  • There is only one transparent model in a scene. The primitives in the model must be sorted, but you can simply render the transparent model last in the scene.
  • There are multiple transparent objects in a scene, but you know that none of them overlap each other from a particular camera angle. Therefore, you can simply render these models last (but in any order).
  • If you have a model that contains some opaque surfaces and some transparent surfaces, then the following situations might apply:
    • You know that the transparent surfaces never face the camera. Therefore you don’t have to worry about the transparent surfaces.
    • The model defines a totally enclosed 3D area and the surfaces behind any transparent surfaces are faces of the same model. Therefore you only have to be concerned with ordering the faces in that particular model, assuming that other models do not intersect in 3D space.

To summarize, if you can use knowledge of a scene to avoid sorting, it is worth the trouble. This is a general principle of all computer graphics – if something is not visible, don’t worry about rendering it correctly.

Sorting for Transparency

To render a scene we render a set of models, where each model is composed of triangles. When we discuss sorting we have two issues:

  • sorting models relative to each other, and
  • sorting primitives (points, lines, and triangles) within models.

Sorting the Primitives of a Model

Given the primitives of a model, we need to sort them based on their distance from the camera. This is problematic because the big idea behind fast rendering is to place the primitive data into a GPU object buffer that never changes. Typically a model is rendered in different sizes, locations, and orientations in a scene using a transformation matrix, while the model data remains static. But now the data has to be re-ordered. We have two basic options:

  • Leave the model data in a GPU object buffer unchanged, and render each primitive (triangle) using a separate call to gl.drawArrays(), or
  • Re-order the triangle data in the GPU object buffer.

The first method produces slower rendering speeds, but is simpler to implement. The second method will render faster, but requires more JavaScript code to implement. The demonstration programs in this lesson use the first method.

For the actual sorting, you should use an insertion sort. Why? Note that you must sort on every rendering operation; you are not sorting just once. If a scene changes very little from one rendering to the next, the relative ordering of models in a scene will not change much. Therefore, assuming you re-use your previous sorting results, you need to sort a list of primitives that is almost sorted. An insertion sort is the fastest way to sort a list that is already almost sorted. (Don’t ever use quick sort or merge sort for a soring task like this. These sorting algorithms are the fastest general purpose sorting methods in common use, but they can’t “quit early” and they don’t have good run-time behaviour on sorted data.)

To sort the triangles that make up a model, we need the vertices of the triangles after the model and view transforms have been applied to it. In addition, we don’t want to move the data in memory, we just want to find their sorted order. Therefore we can perform an “index sort”, where we use indexes into an array of values to keep track of the sorted order, but never actually rearrange the data values. Here is a general algorithm for sorting the triangles that compose a model:

  1. For each triangle of a model:
    1. Transform each vertex of the triangle by the current ModelView transformation.
    2. Determine the vertex that is farthest from the camera. (Since the camera is looking down the -Z axis, this is the minimum value of z.)
    3. Store this vertex’s z-component as the distance of the triangle from the camera.
  2. Perform an insertion sort on the triangles, using the z-component of the vertex that is farthest from the camera as the sorting key.
  3. Render the model:
    • If you leave the GPU object buffer unchanged, loop through the triangles and call gl.drawArrays() once for each triangle.
    • If you create a new 1D array of model data in sorted order and copy it to a GPU object buffer, then make a single call to gl.drawArrays().

The following function initializes an array of index values in preparation for sorting.

var sort_indexes = null;

//-----------------------------------------------------------------------
/**
 * Initialize the sort_indexes array for sorting the model's triangles.
 * This array is re-sorted before each render of a transparent model.
 * @private
 */
function _initialize_sorting() {
  var j;

  if (number_triangles  > 0) {
    sort_indexes = new Array(number_triangles);
    for (j = 0; j < number_triangles; j += 1) {
      sort_indexes[j] = [j, 0.0];  // [index to triangle, distance from camera]
    }
  }
}

Let’s assume that a model is defined by a set of triangles whose vertices are stored in a 1D array of floats – 9 floats per triangle, 3 floats per vertex. The array is organized like this:

vertices = [v1x, v1y, v1z, v2x, v2y, v2z, v3x, v3y, v3z, ...]

The following function sorts indexes into this array of floats. The model’s vertices are multiplied by a ModelView transform that puts the camera at the origin looking down the -Z axis. For a given triangle, the vertex with the smallest z-value is the farthest from the camera.

//-----------------------------------------------------------------------
/**
 * Sort the triangles of a model, back to front, based on their distance
 * from the camera.
 * @param vm_transform Float32Array The transformation to apply to the model vertices.
 */
function _sort_triangles (vm_transform) {
  var j, k, n, which_triangle, vertices, max_z, temp;

  // Step 1: Transform each vertex in a model by the current *ModelView* transformation.
  // Step 2: For each triangle, determine its maximum distance from the camera.
  vertices = model.triangles.vertices;
  for (j = 0; j < number_triangles; j += 1) {

    which_triangle = sort_indexes[j][0];
    k = which_triangle * 3 * 3;
    max_z = 10e10;
    for (n = 0; n < 3; n += 1, k += 3) {
      one_vertex[0] = vertices[k];
      one_vertex[1] = vertices[k + 1];
      one_vertex[2] = vertices[k + 2];
      matrix.multiply(transformed_vertex, vm_transform, one_vertex);

      if (transformed_vertex[2] < max_z) {
        max_z = transformed_vertex[2];
      }
    }

    // Remember this triangle's distance from the camera
    sort_indexes[j][1] = max_z;
  }

  // Step 3: Perform an insertion sort on the triangles, using the vertex
  // that is farthest from the camera as the sorting key.
  for (j = 0; j < number_triangles; j += 1) {
    temp = sort_indexes[j];
    k = j - 1;
    while (k >= 0 && sort_indexes[k][1] > temp[1]) {
      sort_indexes[k + 1] = sort_indexes[k];
      k -= 1;
    }
    sort_indexes[k + 1] = temp;
  }
}

Sorting Models

We need to render the transparent models in a scene from back to front. If the models do not overlap in 3D space, this is just a matter of sorting the models based on their distance from the camera. Since the models do not overlap, you can use any vertex on the model, or a model’s center point, to calculate the distance. For the WebGL demonstration program below, which displays spheres, the distances are calculated using the center point of each sphere.

If two or more transparent models overlap in 3D space, it is not possible to render them correct as independent models. To render them correctly you must combine the models, sort their combined triangles, and then render the triangles from back to front. This is a quandary! We keep models as separate entities so that they can be transformed independently. But for rendering, we need the models to be combined into a single list of primitives. If you combine the models as a preprocessing step, the models can’t be transformed independently. If you combine the models at render time it can greatly slow down your rendering frame rate.

Experimentation 1 (Non-overlapping models)

Please experiment with the following WebGL demonstration program by disabling the animation and rotating the models to study the transparency. Rotate to a view that allows you to see through multiple transparent models with an opaque model in the background. Is the rendering correct? Do you see any models that are rendered incorrectly? (There will be errors if any of the spheres overlap.)

Show: Code Canvas Run Info
 
1
/**
2
 * TransparencyExampleRender.js, By Wayne Brown, Spring 2016
3
 */
4
5
/**
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2015 C. Wayne Brown
9
 *
10
 * Permission is hereby granted, free of charge, to any person obtaining a copy
11
 * of this software and associated documentation files (the "Software"), to deal
12
 * in the Software without restriction, including without limitation the rights
13
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
 * copies of the Software, and to permit persons to whom the Software is
15
 * furnished to do so, subject to the following conditions:
16
 *
17
 * The above copyright notice and this permission notice shall be included in all
18
 * copies or substantial portions of the Software.
19
20
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
 * SOFTWARE.
27
 */
28
29
"use strict";
30
31
var console, Learn_webgl_point4;
32
33
//-------------------------------------------------------------------------
34
// Build, create, copy and render 3D objects specific to a particular
35
// model definition and particular WebGL shaders.
36
//-------------------------------------------------------------------------
37
window.TransparencyExampleRender = function (learn, vshaders_dictionary,
38
                                fshaders_dictionary, models, controls) {
39
40
  // Private variables
41
  var self = this;
42
  var canvas_id = learn.canvas_id;
43
  var out = learn.out;
44
45
  var gl = null;
46
  var program = null;
47
  var a_sphere = null;
48
49
  var number_spheres = 10;
50
  var number_transparent_spheres = 5;
51
  var number_opaque_spheres = number_spheres - number_transparent_spheres;
52
53
  var all_spheres = new Array(number_spheres);
54
55
  var matrix = new window.Learn_webgl_matrix();
56
  var transform = matrix.create();
57
  var view_model_transform = matrix.create();
58
  var projection = matrix.createPerspective(45.0, 1.0, 0.1, 100.0);
59
  var camera = matrix.create();
60
  var scale_matrix = matrix.create();
61
  var translate_matrix = matrix.create();
62
  var eye_x;
63
  var eye_y;
64
  var eye_z;
65
66
  // Public variables that will possibly be used or changed by event handlers.
67
  self.canvas = null;
68
  self.angle_x = 0.0;
69
  self.angle_y = 0.0;
70
  self.scale = 1.0;
71
  self.animate_active = false;
72
73
  // Light model
74
  var P4 = new window.Learn_webgl_point4();
75
  var V = new window.Learn_webgl_vector3();
76
  self.light_position = P4.create(10, 10, 10, 1);
77
  self.light_color = V.create(1, 1, 1); // white light
78
  self.shininess = 30;
79
  self.ambient_color = V.create(0.2, 0.2, 0.2); // low level white light
80
81
  var transformed_center = P4.create();
82
  var sort_indexes = null;
83
84
  //-----------------------------------------------------------------------
85
  /**
86
   * Initialize the sort_indexes array for sorting the transparent models.
87
   * This array is re-sorted before each render of the scene.
88
   * @private
89
   */
90
  function _initialize_sorting() {
91
    var j;
92
93
    sort_indexes = new Array(number_transparent_spheres);
94
95
    for (j = 0; j < number_transparent_spheres; j += 1) {
96
      sort_indexes[j] = [j, 0.0];  // [index to triangle, distance from camera]
97
    }
98
  }
99
100
  /** ---------------------------------------------------------------------
101
   * Update the distance from each transparent sphere to the camera. This
102
   * uses the center of the sphere for the distance, which causes problems
103
   * if two transparent spheres overlap in 3D space.
104
   * @private
105
   */
106
  function _update_distances_to_camera () {
107
    var j, index, position;
108
109
    for (j = 0; j < number_transparent_spheres; j += 1) {
110
      index = sort_indexes[j][0];
111
112
      position = all_spheres[index].position;
113
      matrix.translate(translate_matrix, position[0], position[1], position[2]);
114
115
      // Combine the camera and sphere translation transforms.
116
      matrix.multiplySeries(view_model_transform, camera, translate_matrix);
117
118
      // Calculate where the sphere is in relationship to the camera.
119
      matrix.multiply(transformed_center, view_model_transform, position);
120
121
      // Set this model's distance from the camera
122
      sort_indexes[j][1] = transformed_center[2];
123
    }
124
  }
125
126
  /** ---------------------------------------------------------------------
127
   * Sort the models, back to front, based on their distance from the camera.
128
   * @private
129
   */
130
  function _sort_spheres () {
131
    var j, k, temp;
132
133
    // Perform an insertion sort on the models.
134
    for (j = 0; j < number_transparent_spheres; j += 1) {
135
      temp = sort_indexes[j];
136
      k = j - 1;
137
      while (k >= 0 && sort_indexes[k][1] > temp[1]) {
138
        sort_indexes[k + 1] = sort_indexes[k];
139
        k -= 1;
140
      }
141
      sort_indexes[k + 1] = temp;
142
    }
143
  }
144
145
  //-----------------------------------------------------------------------
146
  self.render = function () {
147
    var j, size, position, x_radians, y_radians, scale, index;
148
149
    // Clear the entire canvas window background with the clear color
150
    // out.display_info("Clearing the screen");
151
    gl.depthMask(true);
152
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
153
154
    // Build the camera transform to circle the origin.
155
    x_radians = matrix.toRadians(self.angle_x);
156
    y_radians = matrix.toRadians(self.angle_y);
157
    scale = Math.cos(x_radians);
158
    eye_x = Math.cos(y_radians) * scale * 20.0;
159
    eye_z = Math.sin(y_radians) * scale * 20.0;
160
    eye_y = Math.sin(x_radians) * 20.0;
161
    matrix.lookAt(camera, eye_x, eye_y, eye_z, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
162
163
    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
164
    // Set the location of the light source
165
    gl.uniform3f(program.u_Light_position, self.light_position[0],
166
        self.light_position[1],
167
        self.light_position[2]);
168
    gl.uniform3fv(program.u_Light_color, self.light_color);
169
    gl.uniform3fv(program.u_Ambient_color, self.ambient_color);
170
    gl.uniform1f(program.u_Shininess, self.shininess);
171
172
    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
173
    // Render the opaque spheres first.
174
    gl.disable(gl.BLEND);
175
176
    for (j = number_transparent_spheres; j < number_spheres; j += 1) {
177
      size = all_spheres[j].size;
178
      position = all_spheres[j].position;
179
180
      matrix.scale(scale_matrix, size, size, size);
181
      matrix.translate(translate_matrix, position[0], position[1], position[2]);
182
183
      // Combine the transforms into a single transformation
184
      matrix.multiplySeries(view_model_transform, camera,
185
                            translate_matrix, scale_matrix);
186
      matrix.multiplySeries(transform, projection, view_model_transform);
187
188
      // Assign a color to the sphere. Make it red if it is the selected object.
189
      a_sphere.setColor(all_spheres[j].color);
190
191
      a_sphere.render(transform, view_model_transform);
192
    }
193
194
    // Sort the transparent models, back to front, in reference to the camera
195
196
    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
197
    // Render the transparent spheres in sorted order.
198
199
    // Enable alpha blending and set the percentage blending factors
200
    gl.enable(gl.BLEND);
201
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
202
203
    // Turn off updating of the z-buffer
204
    gl.depthMask(false);
205
206
    // Update the distance of each sphere from the camera
207
    _update_distances_to_camera();
208
209
    // Sort the models based on their distance from the camera. The center
210
    // point of each sphere is used for the distance.
211
    _sort_spheres();
212
213
    // Render the transparent spheres
214
    for (j = 0; j < number_transparent_spheres; j += 1) {
215
      index = sort_indexes[j][0];
216
217
      size = all_spheres[index].size;
218
      position = all_spheres[index].position;
219
220
      matrix.scale(scale_matrix, size, size, size);
221
      matrix.translate(translate_matrix, position[0], position[1], position[2]);
222
223
      // Combine the transforms into a single transformation
224
      matrix.multiplySeries(view_model_transform, camera,
225
                            translate_matrix, scale_matrix);
226
      matrix.multiplySeries(transform, projection, view_model_transform);
227
228
      // Set the sphere's color.
229
      a_sphere.setColor(all_spheres[index].color);
230
231
      a_sphere.render(transform, view_model_transform);
232
    }
233
234
  };
235
236
  //-----------------------------------------------------------------------
237
  self.delete = function () {
238
239
    // Clean up shader programs
240
    gl.deleteShader(program.vShader);
241
    gl.deleteShader(program.fShader);
242
    gl.deleteProgram(program);
243
    program = null;
244
245
    // Delete each model's VOB
246
    a_sphere.delete();
247
248
    // Delete all of the events associated with this canvas
249
    events.removeAllEventHandlers();
250
251
    // Disable any animation
252
    self.animate_active = false;
253
  };
254
255
  //-----------------------------------------------------------------------
256
  // Object constructor. One-time initialization of the scene.
257
258
  // Get the rendering context for the canvas
259
  self.canvas = learn.getCanvas(canvas_id);
260
  if (self.canvas) {
261
    gl = learn.getWebglContext(self.canvas);
262
  }
263
  if (!gl) {
264
    return;
265
  }
266
267
  // Set up the rendering program and set the state of webgl
268
  program = learn.createProgram(gl, vshaders_dictionary.shader41, fshaders_dictionary.shader41);
269
270
  gl.useProgram(program);
271
272
  gl.enable(gl.DEPTH_TEST);
273
274
  gl.clearColor(0.9, 0.9, 0.9, 1.0);
275
276
  // Create Vertex Object Buffers for the models
277
  a_sphere = new window.Learn_webgl_model_render_41(gl, program,
278
                                          models.Icosphere, out);
279
280
  // Create a set of random positions, colors, and sizes for a group of spheres.
281
  var j, position_x, position_y, position_z, color;
282
  for (j = 0; j < number_spheres; j += 1) {
283
    position_x = Math.random() * 10 - 5.0;
284
    position_y = Math.random() * 10 - 5.0;
285
    position_z = Math.random() * 10 - 5.0;
286
    if (j < number_transparent_spheres) {
287
      color = new Float32Array([Math.random(), Math.random(), Math.random(), Math.random()]);
288
    } else {
289
      color = new Float32Array([Math.random(), Math.random(), Math.random(), 1.0]);
290
    }
291
    all_spheres[j] = { position: P4.create(position_x, position_y, position_z),
292
                       size: Math.random() * 5,
293
                       color: color
294
                     };
295
  }
296
297
  // Set up callbacks for user and timer events
298
  var events;
299
  events = new window.TransparencyExampleEvents(self, controls);
300
  events.animate();
301
302
  // Initialize the array of model indexes that will be used for sorting.
303
  _initialize_sorting();
304
};
305
306
307
312
 
1
/**
2
 * simple_model_render_40.js, By Wayne Brown, Fall 2015
3
 */
4
5
/**
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2015 C. Wayne Brown
9
 *
10
 * Permission is hereby granted, free of charge, to any person obtaining a copy
11
 * of this software and associated documentation files (the "Software"), to deal
12
 * in the Software without restriction, including without limitation the rights
13
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
 * copies of the Software, and to permit persons to whom the Software is
15
 * furnished to do so, subject to the following conditions:
16
 *
17
 * The above copyright notice and this permission notice shall be included in all
18
 * copies or substantial portions of the Software.
19
20
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
 * SOFTWARE.
27
 */
28
29
"use strict";
30
31
//-------------------------------------------------------------------------
32
/**
33
 * Given a model description, create the buffer objects needed to render
34
 * the model. This is very closely tied to the shader implementations.
35
 * @param gl Object The WebGL state and API
36
 * @param select_program Object The shader program the will render the model for selection.
37
 * @param visible_program Object The shader program the will render the model for selection.
38
 * @param model Simple_model The model data.
39
 * @param out Object Can display messages to the webpage.
40
 * @constructor
41
 */
42
window.Learn_webgl_model_render_41 = function (gl, program, model, out) {
43
44
  var self = this;
45
46
  // Variables to remember so the model can be rendered.
47
  var number_triangles = 0;
48
  var triangles_vertex_buffer_id = null;
49
  var triangles_color_buffer_id = null;
50
  var triangles_normal_buffer_id = null;
51
  var triangles_smooth_normal_buffer_id = null;
52
53
  var number_lines = 0;
54
  var lines_vertex_buffer_id = null;
55
  var lines_color_buffer_id = null;
56
57
  var number_points = 0;
58
  var points_vertex_buffer_id = null;
59
  var points_color_buffer_id = null;
60
61
  var model_color = null;
62
  var contains_transparent_surfaces = false;
63
64
  // For sorting the vertices of a triangle
65
  var matrix = new window.Learn_webgl_matrix();
66
  var p4 = new window.Learn_webgl_point4();
67
  var one_vertex = p4.create();
68
  var transformed_vertex = p4.create();
69
  var sort_indexes = null;
70
71
  //-----------------------------------------------------------------------
72
  /**
73
   * Initialize the sort_indexes array for sorting the model's triangles.
74
   * This array is re-sorted before each render of a transparent model.
75
   * @private
76
   */
77
  function _initialize_sorting() {
78
    var j;
79
80
    if (number_triangles  > 0) {
81
      sort_indexes = new Array(number_triangles);
82
      for (j = 0; j < number_triangles; j += 1) {
83
        sort_indexes[j] = [j, 0.0];  // [index to triangle, distance from camera]
84
      }
85
    }
86
  }
87
88
  //-----------------------------------------------------------------------
89
  function _createBufferObject(data) {
90
    // Create a buffer object
91
    var buffer_id;
92
93
    buffer_id = gl.createBuffer();
94
    if (!buffer_id) {
95
      out.displayError('Failed to create the buffer object for ' + model.name);
96
      return null;
97
    }
98
99
    // Make the buffer object the active buffer.
100
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer_id);
101
102
    // Upload the data for this buffer object to the GPU.
103
    gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
104
105
    return buffer_id;
106
  }
107
108
  //-----------------------------------------------------------------------
109
  /**
110
   * Create the buffer objects needed and upload the data to the GPU
111
   * @private
112
   */
113
  function _buildBufferObjects() {
114
115
    // Build the buffers for the triangles
116
    if (model.triangles !== null && model.triangles.vertices.length > 0) {
117
      number_triangles = model.triangles.vertices.length / 3 / 3;
118
      triangles_vertex_buffer_id = _createBufferObject(model.triangles.vertices);
119
      triangles_color_buffer_id = _createBufferObject(model.triangles.colors);
120
      triangles_normal_buffer_id = _createBufferObject(model.triangles.flat_normals);
121
      triangles_smooth_normal_buffer_id = _createBufferObject(model.triangles.smooth_normals);
122
123
      sort_indexes = new Array(number_triangles);
124
    }
125
126
    // Build the buffers for the lines
127
    if (model.lines !== null && model.lines.vertices.length > 0) {
128
      number_lines = model.lines.vertices.length / 3 / 2;
129
      lines_vertex_buffer_id = _createBufferObject(model.lines.vertices);
130
      lines_color_buffer_id = _createBufferObject(model.lines.colors);
131
    }
132
133
    // Build the buffers for the points
134
    if (model.points !== null && model.points.vertices.length > 0) {
135
      number_points = model.points.vertices.length / 3; // 3 components per vertex
136
      points_vertex_buffer_id = _createBufferObject(model.points.vertices);
137
      points_color_buffer_id = _createBufferObject(model.points.colors);
138
    }
139
140
  }
141
142
  //-----------------------------------------------------------------------
143
  /**
144
   * Get the location of the shader variables in the shader program.
145
   * @private
146
   */
147
  function _getLocationOfShaderVariables() {
148
    // Get the location of the shader variables
149
    program.u_PVM_transform = gl.getUniformLocation(program, "u_PVM_transform");
150
    program.u_VM_transform = gl.getUniformLocation(program, "u_VM_transform");
151
152
    program.u_Light_position = gl.getUniformLocation(program, "u_Light_position");
153
    program.u_Light_color = gl.getUniformLocation(program, "u_Light_color");
154
    program.u_Ambient_color = gl.getUniformLocation(program, "u_Ambient_color");
155
    program.u_Shininess = gl.getUniformLocation(program, "u_Shininess");
156
157
    program.a_Vertex = gl.getAttribLocation(program, 'a_Vertex');
158
    //visible_program.a_Color = gl.getAttribLocation(visible_program, 'a_Color');
159
    program.a_Vertex_normal = gl.getAttribLocation(program, 'a_Vertex_normal');
160
161
    program.u_Model_color = gl.getUniformLocation(program, "u_Model_color");
162
  }
163
164
  //-----------------------------------------------------------------------
165
  // These one-time tasks set up the rendering of the models.
166
  _buildBufferObjects();
167
  _getLocationOfShaderVariables();
168
  _initialize_sorting();
169
170
  //-----------------------------------------------------------------------
171
  /**
172
   * Set the model's uniform color.
173
   * @param color Array An RGBA array used for the object's selection ID
174
   */
175
  self.setColor = function (color) {
176
    model_color = color;
177
    if (color[3] !== 1.0) {
178
      contains_transparent_surfaces = true;
179
    }
180
  };
181
182
  //-----------------------------------------------------------------------
183
  /**
184
   * Delete the Buffer Objects associated with this model.
185
   */
186
  self.delete = function () {
187
    if (number_triangles > 0) {
188
      gl.deleteBuffer(triangles_vertex_buffer_id);
189
      gl.deleteBuffer(triangles_color_buffer_id);
190
      gl.deleteBuffer(triangles_normal_buffer_id);
191
      gl.deleteBuffer(triangles_smooth_normal_buffer_id);
192
    }
193
    if (number_lines > 0) {
194
      gl.deleteBuffer(lines_vertex_buffer_id);
195
      gl.deleteBuffer(lines_color_buffer_id);
196
    }
197
    if (number_points > 0) {
198
      gl.deleteBuffer(points_vertex_buffer_id);
199
      gl.deleteBuffer(points_color_buffer_id);
200
    }
201
  };
202
203
  //-----------------------------------------------------------------------
204
  /**
205
   * Render the triangles in the model.
206
   * @private
207
   */
208
  function _renderTriangles() {
209
    var j;
210
211
    if (number_triangles > 0) {
212
      // Activate the model's triangle vertex object buffer (VOB)
213
      gl.bindBuffer(gl.ARRAY_BUFFER, triangles_vertex_buffer_id);
214
215
      // Bind the vertices VOB to the 'a_Vertex' shader variable
216
      //var stride = self.vertices3[0].BYTES_PER_ELEMENT*3;
217
      gl.vertexAttribPointer(program.a_Vertex, 3, gl.FLOAT, false, 0, 0);
218
      gl.enableVertexAttribArray(program.a_Vertex);
219
220
      // Activate the model's triangle color object buffer
221
      //gl.bindBuffer(gl.ARRAY_BUFFER, triangles_color_buffer_id);
222
223
      // Bind the colors VOB to the 'a_Color' shader variable
224
      //gl.vertexAttribPointer(visible_program.a_Color, 3, gl.FLOAT, false, 0, 0);
225
      //gl.enableVertexAttribArray(visible_program.a_Color);
226
227
      // Activate the model's triangle normal vector object buffer
228
      gl.bindBuffer(gl.ARRAY_BUFFER, triangles_smooth_normal_buffer_id);
229
230
      // Bind the normal vectors VOB to the 'a_Normal' shader variable
231
      gl.vertexAttribPointer(program.a_Vertex_normal, 3, gl.FLOAT, false, 0, 0);
232
      gl.enableVertexAttribArray(program.a_Vertex_normal);
233
234
      if (contains_transparent_surfaces) {
235
        // Draw all of the triangles in sorted order
236
        for (j = 0; j < number_triangles; j += 1) {
237
          gl.drawArrays(gl.TRIANGLES, sort_indexes[j][0] * 3, 3);
238
        }
239
      } else {
240
          gl.drawArrays(gl.TRIANGLES, 0, number_triangles * 3);
241
      }
242
    }
243
  }
244
245
  //-----------------------------------------------------------------------
246
  /**
247
   * Sort the triangles of a model, back to front, based on their distance
248
   * from the camera.
249
   * @param vm_transform Float32Array The transformation to apply to the model vertices.
250
   */
251
  function _sort_triangles (vm_transform) {
252
    var j, k, n, which_triangle, vertices, max_z, temp;
253
254
    // Step 1: Transform each vertex in a model by the current *ModelView* transformation.
255
    // Step 2: For each triangle, determine its maximum distance from the camera.
256
    vertices = model.triangles.vertices;
257
    for (j = 0; j < number_triangles; j += 1) {
258
259
      which_triangle = sort_indexes[j][0];
260
      k = which_triangle * 3 * 3;
261
      max_z = 10e10;
262
      for (n = 0; n < 3; n += 1, k += 3) {
263
        one_vertex[0] = vertices[k];
264
        one_vertex[1] = vertices[k + 1];
265
        one_vertex[2] = vertices[k + 2];
266
        matrix.multiply(transformed_vertex, vm_transform, one_vertex);
267
268
        if (transformed_vertex[2] < max_z) {
269
          max_z = transformed_vertex[2];
270
        }
271
      }
272
273
      // Remember this triangle's distance from the camera
274
      sort_indexes[j][1] = max_z;
275
    }
276
277
    // Step 3: Perform an insertion sort on the triangles, using the vertex
278
    // that is farthest from the camera as the sorting key.
279
    for (j = 0; j < number_triangles; j += 1) {
280
      temp = sort_indexes[j];
281
      k = j - 1;
282
      while (k >= 0 && sort_indexes[k][1] > temp[1]) {
283
        sort_indexes[k + 1] = sort_indexes[k];
284
        k -= 1;
285
      }
286
      sort_indexes[k + 1] = temp;
287
    }
288
  }
289
290
  //-----------------------------------------------------------------------
291
  /**
292
   * Render the model.
293
   * @param pvm_transform Float32Array The projection, view, model transform.
294
   * @param vm_transform Float32Array The view, model transform.
295
   */
296
  self.render = function (pvm_transform, vm_transform) {
297
298
    if (contains_transparent_surfaces) {
299
      _sort_triangles (vm_transform);
300
    }
301
302
    // Set the transform for all the faces, lines, and points
303
    gl.uniformMatrix4fv(program.u_PVM_transform, false, pvm_transform);
304
    gl.uniformMatrix4fv(program.u_VM_transform, false, vm_transform);
305
    gl.uniform4fv(program.u_Model_color, model_color);
306
307
    _renderTriangles();
308
  };
309
310
};
311
312
./transparency_example/transparency_example.html

An example of object transparency.

Please use a browser that supports "canvas"
Animate
Shader errors, Shader warnings, Javascript info
Shader '../lib/shaders/shader41.frag' has been downloaded.
In _initializeRendering: 1 of 5 files have been retrieved
Shader '../lib/shaders/shader01.frag' has been downloaded.
In _initializeRendering: 2 of 5 files have been retrieved
Shader '../lib/shaders/shader41.vert' has been downloaded.
In _initializeRendering: 3 of 5 files have been retrieved
Model '../lib/models/simple_sphere.obj' has been downloaded.
Found these material MTL files: 0
In _initializeRendering: 4 of 6 files have been retrieved
Shader '../lib/shaders/shader01.vert' has been downloaded.
In _initializeRendering: 5 of 6 files have been retrieved
Materials file '../lib/models/simple_sphere.mtl' has been downloaded.
In _initializeRendering: 6 of 6 files have been retrieved
All files have been retrieved!
Created model: Icosphere
Open this webgl program in a new tab or window

Experiments on the transparency_example_render_js code:

  • If you restart the program you will get different combinations of random spheres.
  • In lines 49-50 you can set the number of spheres to render and the number of spheres that are transparent. Try different combinations of models.
    • If you increase the numbers you will probably see spheres that overlap and therefore render incorrectly.
    • Set the numbers back to 10 and 5 before continuing.
  • In line 200, comment out gl.enable(gl.BLEND);. Notice that all transparency is now gone. Without color “blending” there is no transparency. (Enable blending before continuing.)
  • Don’t sort the models by commenting out line 211. You will get some strange visual effects because the the motion of the transparent spheres will not match your mental understanding of their position in the 3D world. (Turn the sorting back on before continuing.)

Experiments on the learn_webgl_model_render_41.js code:

  • In line 299, comment out the call to the _sort_triangles function. Notice the “blotching” effect on the rendered spheres. This is because the triangles are being rendered in the order they are defined in the buffer object. This causes some of the triangles that are closest to the camera to be rendered before the object behind the triangle has the correct color. This causes the wrong colors to be blended together.

Experimentation 2 (Overlapping models)

This demonstration program allows you visualize what happens when transparent models overlap. Disable the animation and manually rotate the view. Notice that the rendering has dramatic changes when one sphere gets closer to the camera than the other spheres. It is not possible to render each sphere independently and render all of the triangles in the correct back-to-front order.

Show: Code Canvas Run Info
304
 
1
/**
2
 * TransparencyExampleRender.js, By Wayne Brown, Spring 2016
3
 */
4
5
/**
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2015 C. Wayne Brown
9
 *
10
 * Permission is hereby granted, free of charge, to any person obtaining a copy
11
 * of this software and associated documentation files (the "Software"), to deal
12
 * in the Software without restriction, including without limitation the rights
13
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
 * copies of the Software, and to permit persons to whom the Software is
15
 * furnished to do so, subject to the following conditions:
16
 *
17
 * The above copyright notice and this permission notice shall be included in all
18
 * copies or substantial portions of the Software.
19
20
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
 * SOFTWARE.
27
 */
28
29
"use strict";
30
31
var console, Learn_webgl_point4;
32
33
//-------------------------------------------------------------------------
34
// Build, create, copy and render 3D objects specific to a particular
35
// model definition and particular WebGL shaders.
36
//-------------------------------------------------------------------------
37
window.TransparencyExample2Render = function (learn, vshaders_dictionary,
38
                                fshaders_dictionary, models, controls) {
39
40
  // Private variables
41
  var self = this;
42
  var canvas_id = learn.canvas_id;
43
  var out = learn.out;
44
45
  var gl = null;
46
  var program = null;
47
  var a_sphere = null;
48
49
  var number_spheres = 3;
50
  var number_transparent_spheres = 3;
51
  var number_opaque_spheres = number_spheres - number_transparent_spheres;
52
53
  var all_spheres = new Array(number_spheres);
54
55
  var matrix = new window.Learn_webgl_matrix();
56
  var transform = matrix.create();
57
  var view_model_transform = matrix.create();
58
  var projection = matrix.createPerspective(45.0, 1.0, 0.1, 100.0);
59
  var camera = matrix.create();
60
  var scale_matrix = matrix.create();
61
  var translate_matrix = matrix.create();
62
  var eye_x;
63
  var eye_y;
64
  var eye_z;
65
66
  // Public variables that will possibly be used or changed by event handlers.
67
  self.canvas = null;
68
  self.angle_x = 0.0;
69
  self.angle_y = 0.0;
70
  self.scale = 1.0;
71
  self.animate_active = false;
72
73
  // Light model
74
  var P4 = new window.Learn_webgl_point4();
75
  var V = new window.Learn_webgl_vector3();
76
  self.light_position = P4.create(10, 10, 10, 1);
77
  self.light_color = V.create(1, 1, 1); // white light
78
  self.shininess = 30;
79
  self.ambient_color = V.create(0.2, 0.2, 0.2); // low level white light
80
81
  var transformed_center = P4.create();
82
  var sort_indexes = null;
83
84
  //-----------------------------------------------------------------------
85
  /**
86
   * Initialize the sort_indexes array for sorting the transparent models.
87
   * This array is re-sorted before each render of the scene.
88
   * @private
89
   */
90
  function _initialize_sorting() {
91
    var j;
92
93
    sort_indexes = new Array(number_transparent_spheres);
94
95
    for (j = 0; j < number_transparent_spheres; j += 1) {
96
      sort_indexes[j] = [j, 0.0];  // [index to triangle, distance from camera]
97
    }
98
  }
99
100
  /** ---------------------------------------------------------------------
101
   * Update the distance from each transparent sphere to the camera. This
102
   * uses the center of the sphere for the distance, which causes problems
103
   * if two transparent spheres overlap in 3D space.
104
   * @private
105
   */
106
  function _update_distances_to_camera () {
107
    var j, index, position;
108
109
    for (j = 0; j < number_transparent_spheres; j += 1) {
110
      index = sort_indexes[j][0];
111
112
      position = all_spheres[index].position;
113
      matrix.translate(translate_matrix, position[0], position[1], position[2]);
114
115
      // Combine the camera and sphere translation transforms.
116
      matrix.multiplySeries(view_model_transform, camera, translate_matrix);
117
118
      // Calculate where the sphere is in relationship to the camera.
119
      matrix.multiply(transformed_center, view_model_transform, position);
120
121
      // Set this model's distance from the camera
122
      sort_indexes[j][1] = transformed_center[2];
123
    }
124
  }
125
126
  /** ---------------------------------------------------------------------
127
   * Sort the models, back to front, based on their distance from the camera.
128
   * @private
129
   */
130
  function _sort_spheres () {
131
    var j, k, temp;
132
133
    // Perform an insertion sort on the models.
134
    for (j = 0; j < number_transparent_spheres; j += 1) {
135
      temp = sort_indexes[j];
136
      k = j - 1;
137
      while (k >= 0 && sort_indexes[k][1] > temp[1]) {
138
        sort_indexes[k + 1] = sort_indexes[k];
139
        k -= 1;
140
      }
141
      sort_indexes[k + 1] = temp;
142
    }
143
  }
144
145
  //-----------------------------------------------------------------------
146
  self.render = function () {
147
    var j, size, position, x_radians, y_radians, scale, index;
148
149
    // Clear the entire canvas window background with the clear color
150
    // out.display_info("Clearing the screen");
151
    gl.depthMask(true);
152
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
153
154
    // Build the camera transform to circle the origin.
155
    x_radians = matrix.toRadians(self.angle_x);
156
    y_radians = matrix.toRadians(self.angle_y);
157
    scale = Math.cos(x_radians);
158
    eye_x = Math.cos(y_radians) * scale * 20.0;
159
    eye_z = Math.sin(y_radians) * scale * 20.0;
160
    eye_y = Math.sin(x_radians) * 20.0;
161
    matrix.lookAt(camera, eye_x, eye_y, eye_z, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
162
163
    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
164
    // Set the location of the light source
165
    gl.uniform3f(program.u_Light_position, self.light_position[0],
166
        self.light_position[1],
167
        self.light_position[2]);
168
    gl.uniform3fv(program.u_Light_color, self.light_color);
169
    gl.uniform3fv(program.u_Ambient_color, self.ambient_color);
170
    gl.uniform1f(program.u_Shininess, self.shininess);
171
172
    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
173
    // Render the opaque spheres first.
174
    gl.disable(gl.BLEND);
175
176
    for (j = number_transparent_spheres; j < number_spheres; j += 1) {
177
      size = all_spheres[j].size;
178
      position = all_spheres[j].position;
179
180
      matrix.scale(scale_matrix, size, size, size);
181
      matrix.translate(translate_matrix, position[0], position[1], position[2]);
182
183
      // Combine the transforms into a single transformation
184
      matrix.multiplySeries(view_model_transform, camera,
185
                            translate_matrix, scale_matrix);
186
      matrix.multiplySeries(transform, projection, view_model_transform);
187
188
      // Assign a color to the sphere. Make it red if it is the selected object.
189
      a_sphere.setColor(all_spheres[j].color);
190
191
      a_sphere.render(transform, view_model_transform);
192
    }
193
194
    // Sort the transparent models, back to front, in reference to the camera
195
196
    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
197
    // Render the transparent spheres in sorted order.
198
199
    // Enable alpha blending and set the percentage blending factors
200
    gl.enable(gl.BLEND);
201
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
202
203
    // Turn off updating of the z-buffer
204
    gl.depthMask(false);
205
206
    // Update the distance of each sphere from the camera
207
    _update_distances_to_camera();
208
209
    // Sort the models based on their distance from the camera. The center
210
    // point of each sphere is used for the distance.
211
    _sort_spheres();
212
213
    // Render the transparent spheres
214
    for (j = 0; j < number_transparent_spheres; j += 1) {
215
      index = sort_indexes[j][0];
216
217
      size = all_spheres[index].size;
218
      position = all_spheres[index].position;
219
220
      matrix.scale(scale_matrix, size, size, size);
221
      matrix.translate(translate_matrix, position[0], position[1], position[2]);
222
223
      // Combine the transforms into a single transformation
224
      matrix.multiplySeries(view_model_transform, camera,
225
                            translate_matrix, scale_matrix);
226
      matrix.multiplySeries(transform, projection, view_model_transform);
227
228
      // Set the sphere's color.
229
      a_sphere.setColor(all_spheres[index].color);
230
231
      a_sphere.render(transform, view_model_transform);
232
    }
233
234
  };
235
236
  //-----------------------------------------------------------------------
237
  self.delete = function () {
238
239
    // Clean up shader programs
240
    gl.deleteShader(program.vShader);
241
    gl.deleteShader(program.fShader);
242
    gl.deleteProgram(program);
243
    program = null;
244
245
    // Delete each model's VOB
246
    a_sphere.delete();
247
248
    // Delete all of the events associated with this canvas
249
    events.removeAllEventHandlers();
250
251
    // Disable any animation
252
    self.animate_active = false;
253
  };
254
255
  //-----------------------------------------------------------------------
256
  // Object constructor. One-time initialization of the scene.
257
258
  // Get the rendering context for the canvas
259
  self.canvas = learn.getCanvas(canvas_id);
260
  if (self.canvas) {
261
    gl = learn.getWebglContext(self.canvas);
262
  }
263
  if (!gl) {
264
    return;
265
  }
266
267
  // Set up the rendering program and set the state of webgl
268
  program = learn.createProgram(gl, vshaders_dictionary.shader41, fshaders_dictionary.shader41);
269
270
  gl.useProgram(program);
271
272
  gl.enable(gl.DEPTH_TEST);
273
274
  gl.clearColor(0.9, 0.9, 0.9, 1.0);
275
276
  // Create Vertex Object Buffers for the models
277
  a_sphere = new window.Learn_webgl_model_render_41(gl, program,
278
                                          models.Icosphere, out);
279
280
281
  all_spheres[0] = { position: P4.create(-2, 0, 0),
282
                     size: 3,
283
                     color: new Float32Array([1.0, 0.0, 0.0, 0.4])
284
                   };
285
  all_spheres[1] = { position: P4.create(2, 0, 0),
286
                     size: 3,
287
                     color: new Float32Array([0.0, 1.0, 0.0, 0.4])
288
                   };
289
  all_spheres[2] = { position: P4.create(0, 0, -2),
290
                     size: 3,
291
                     color: new Float32Array([0.0, 0.0, 1.0, 0.4])
292
                   };
293
294
  // Set up callbacks for user and timer events
295
  var events;
296
  events = new window.TransparencyExample2Events(self, controls);
297
  events.animate();
298
299
  // Initialize the array of model indexes that will be used for sorting.
300
  _initialize_sorting();
301
};
302
303
304
./transparency_example2/transparency_example2.html

An example of three transparent objects that overlap. Notice how the rendering changes when the sorted ordering of the objects change.

Please use a browser that supports "canvas"
Animate
Shader errors, Shader warnings, Javascript info
Shader '../lib/shaders/shader01.vert' has been downloaded.
In _initializeRendering: 1 of 5 files have been retrieved
Shader '../lib/shaders/shader41.vert' has been downloaded.
In _initializeRendering: 2 of 5 files have been retrieved
Shader '../lib/shaders/shader41.frag' has been downloaded.
In _initializeRendering: 3 of 5 files have been retrieved
Model '../lib/models/sphere3.obj' has been downloaded.
Found these material MTL files: 0
In _initializeRendering: 4 of 6 files have been retrieved
Shader '../lib/shaders/shader01.frag' has been downloaded.
In _initializeRendering: 5 of 6 files have been retrieved
Materials file '../lib/models/sphere3.mtl' has been downloaded.
In _initializeRendering: 6 of 6 files have been retrieved
All files have been retrieved!
Created model: Icosphere
Open this webgl program in a new tab or window

Please experiment with enabling and disabling “writing to the zbuffer” by commenting out gl.depthMask(false) in line 204. In normal operation, the z-buffer algorithm updates the zbuffer to hold the distance of the closest object to the camera from a particular pixel. So with “writing to the zbuffer” enabled, gl.depthMask(true), the color buffer will only be updated with a new color if an object closer to the camera is being rendered. If you have sorted your models from back to front and are rendering them in that order, you can leave “writing to the zbuffer” enabled and everything works fine, except when two or more transparent models overlap in 3D space.

When “writing to the zbuffer” is disabled, gl.depthMask(false), you get a reasonable rendering, but the rendering is wrong and the objects will be rendered differently as the models change their relative location to the camera.

When “writing to the zbuffer” is enabled, gl.depthMask(true), you get a more accurate rendering of the model’s intersections, but you lose some of the interior surfaces because the zbuffer does not allow the “behind” surfaces to be rendered.

In summary, when transparent models overlap, you get the wrong results whether you enable or disable “writing to the zbuffer.” For a particular situation you need to decide which result gives the “better” visual results.

Experimentation 3 (Combined models)

This demonstration program displays a correct rendering of three overlapping spheres. It is created by combining the models into a single model that renders all triangles from back-to-front. To achieve this rendering each vertex must store a unique RGBA value. Notice that as you rotate the single model there is some visual artifacts at the intersections of the three spheres. This is because there are some triangles at the intersection locations that are being rendered in the wrong order. The visual artifacts could be eliminated by subdividing the triangles around the intersection locations – at the cost of slower rendering.

Show: Code Canvas Run Info
163
 
1
/**
2
 * TransparencyExampleRender.js, By Wayne Brown, Spring 2016
3
 */
4
5
/**
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2015 C. Wayne Brown
9
 *
10
 * Permission is hereby granted, free of charge, to any person obtaining a copy
11
 * of this software and associated documentation files (the "Software"), to deal
12
 * in the Software without restriction, including without limitation the rights
13
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
 * copies of the Software, and to permit persons to whom the Software is
15
 * furnished to do so, subject to the following conditions:
16
 *
17
 * The above copyright notice and this permission notice shall be included in all
18
 * copies or substantial portions of the Software.
19
20
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
 * SOFTWARE.
27
 */
28
29
"use strict";
30
31
var console, Learn_webgl_point4;
32
33
//-------------------------------------------------------------------------
34
// Build, create, copy and render 3D objects specific to a particular
35
// model definition and particular WebGL shaders.
36
//-------------------------------------------------------------------------
37
window.TransparencyExample3Render = function (learn, vshaders_dictionary,
38
                                fshaders_dictionary, models, controls) {
39
40
  // Private variables
41
  var self = this;
42
  var canvas_id = learn.canvas_id;
43
  var out = learn.out;
44
45
  var gl = null;
46
  var program = null;
47
  var b_sphere = null;
48
49
  var matrix = new window.Learn_webgl_matrix();
50
  var transform = matrix.create();
51
  var projection = matrix.createPerspective(45.0, 1.0, 0.1, 100.0);
52
  var camera = matrix.create();
53
  var eye_x;
54
  var eye_y;
55
  var eye_z;
56
57
  // Public variables that will possibly be used or changed by event handlers.
58
  self.canvas = null;
59
  self.angle_x = 0.0;
60
  self.angle_y = 0.0;
61
  self.scale = 1.0;
62
  self.animate_active = false;
63
64
  // Light model
65
  var P4 = new window.Learn_webgl_point4();
66
  var V = new window.Learn_webgl_vector3();
67
  self.light_position = P4.create(10, 10, 10, 1);
68
  self.light_color = V.create(1, 1, 1); // white light
69
  self.shininess = 30;
70
  self.ambient_color = V.create(0.2, 0.2, 0.2); // low level white light
71
72
  //-----------------------------------------------------------------------
73
  self.render = function () {
74
    var x_radians, y_radians, scale;
75
76
    // Clear the entire canvas window background with the clear color
77
    // out.display_info("Clearing the screen");
78
    gl.depthMask(true);
79
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
80
81
    // Build the camera transform to circle the origin.
82
    x_radians = matrix.toRadians(self.angle_x);
83
    y_radians = matrix.toRadians(self.angle_y);
84
    scale = Math.cos(x_radians);
85
    eye_x = Math.cos(y_radians) * scale * 20.0;
86
    eye_z = Math.sin(y_radians) * scale * 20.0;
87
    eye_y = Math.sin(x_radians) * 20.0;
88
    matrix.lookAt(camera, eye_x, eye_y, eye_z, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
89
90
    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
91
    // Set the location of the light source
92
    gl.uniform3f(program.u_Light_position, self.light_position[0],
93
        self.light_position[1],
94
        self.light_position[2]);
95
    gl.uniform3fv(program.u_Light_color, self.light_color);
96
    gl.uniform3fv(program.u_Ambient_color, self.ambient_color);
97
    gl.uniform1f(program.u_Shininess, self.shininess);
98
99
    // Enable alpha blending and set the percentage blending factors
100
    gl.enable(gl.BLEND);
101
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
102
103
    // Turn off updating of the z-buffer
104
    //gl.depthMask(false);
105
106
    // Render the transparent spheres
107
    matrix.multiplySeries(transform, projection, camera);
108
    b_sphere.render(transform, camera);
109
110
  };
111
112
  //-----------------------------------------------------------------------
113
  self.delete = function () {
114
115
    // Clean up shader programs
116
    gl.deleteShader(program.vShader);
117
    gl.deleteShader(program.fShader);
118
    gl.deleteProgram(program);
119
    program = null;
120
121
    // Delete each model's VOB
122
    a_sphere.delete();
123
124
    // Delete all of the events associated with this canvas
125
    events.removeAllEventHandlers();
126
127
    // Disable any animation
128
    self.animate_active = false;
129
  };
130
131
  //-----------------------------------------------------------------------
132
  // Object constructor. One-time initialization of the scene.
133
134
  // Get the rendering context for the canvas
135
  self.canvas = learn.getCanvas(canvas_id);
136
  if (self.canvas) {
137
    gl = learn.getWebglContext(self.canvas);
138
  }
139
  if (!gl) {
140
    return;
141
  }
142
143
  // Set up the rendering program and set the state of webgl
144
  program = learn.createProgram(gl, vshaders_dictionary.shader42, fshaders_dictionary.shader42);
145
146
  gl.useProgram(program);
147
148
  gl.enable(gl.DEPTH_TEST);
149
150
  gl.clearColor(0.9, 0.9, 0.9, 1.0);
151
152
  // Create Vertex Object Buffers for the models
153
  b_sphere = new window.Learn_webgl_model_render_42(gl, program,
154
                                          models.Sphere, out);
155
156
  // Set up callbacks for user and timer events
157
  var events;
158
  events = new window.TransparencyExample3Events(self, controls);
159
  events.animate();
160
};
161
162
163
./transparency_example3/transparency_example3.html

An example of three transparent spheres (modeled as a single object) that overlap.

Please use a browser that supports "canvas"
Animate
Shader errors, Shader warnings, Javascript info
Shader '../lib/shaders/shader01.frag' has been downloaded.
In _initializeRendering: 1 of 6 files have been retrieved
Shader '../lib/shaders/shader01.vert' has been downloaded.
In _initializeRendering: 2 of 6 files have been retrieved
Shader '../lib/shaders/shader42.vert' has been downloaded.
In _initializeRendering: 3 of 6 files have been retrieved
Shader '../lib/shaders/shader42.frag' has been downloaded.
In _initializeRendering: 4 of 6 files have been retrieved
Model '../lib/models/three_spheres_aligned.obj' has been downloaded.
Found these material MTL files: 0
In _initializeRendering: 5 of 7 files have been retrieved
Model '../lib/models/three_spheres.obj' has been downloaded.
Found these material MTL files: 0
In _initializeRendering: 6 of 8 files have been retrieved
Materials file '../lib/models/three_spheres_aligned.mtl' has been downloaded.
In _initializeRendering: 7 of 8 files have been retrieved
Open this webgl program in a new tab or window

Alpha Blending (All the details)

The concept of blending the color that is already in the color buffer with a new color from a rendered model has been generalized to allow for a variety of blending factors. When you enable blending in the graphics pipeline, the rendering algorithm looks like this:

void renderPixel(x, y, z, color) {
  if (z < zbuffer[x][y]) {
    zbuffer[x][y] = z;
    colorbuffer[x][y] = (colorbuffer[x][y] * percent1) + (color * percent2);
  }
}

The color in the color buffer is called the “destination color”. The color of the object to be rendered is called the “source color”. And the percentage values are called “factors”. So the highlighted equation in the above pseudocode becomes:

colorbuffer[x][y] = (colorbuffer[x][y] * dst_factor) + (color * src_factor);

where dst_factor and src_factor are each 3-component factors and the multiplication is component-wise. For example, if the color is (0.2, 0.3, 0.4) and the src_factor is (0.5, 0.6, 0.7), then the result of the multiplication color * src_factor is (0.10, 0.18, 0.28). Hopefully is it obvious, but when you see src it refers to the “source color” or the “source factor”. Likewise, dst refers to the “destination color” or the “destination factor”.

I don’t like the names “source” and “destination”, but we need to use them so you will understand the constants that are used to specify the percentages. You don’t specify the factors explicitly; you specify an equation for calculating the factors from the color values. We will use the following names for the components of the color values:

colorbuffer[x][y] --> (dst_red, dst_green, dst_blue, dst_alpha)
color             --> (src_red, src_green, src_blue, src_alpha)

You can select from the following equations for calculating a “factor”. Any of these can be used for the dst_factor and/or the src_factor.

WebGL ENUM constant Resulting factor Alpha value
gl.ZERO (0.0, 0.0, 0.0) 0.0
gl.ONE (1.0, 1.0, 1.0) 1.0
gl.SRC_COLOR (src_red, src_green, src_blue) src_alpha
gl.ONE_MINUS_SRC_COLOR (1 - src_red, 1 - src_green, 1 - src_blue) 1 - src_alpha
gl.DST_COLOR (dst_red, dst_green, dst_blue) dst_alpha
gl.ONE_MINUS_DST_COLOR (1 - dst_red, 1- dst_green, 1- dst_blue) 1 - dst_alpha
gl.SRC_ALPHA (src_alpha, src_alpha, src_alpha) src_alpha
gl.ONE_MINUS_SRC_ALPHA (1 - src_alpha, 1- src_alpha, 1 - src_alpha) 1 - src_alpha
gl.DST_ALPHA (dst_alpha, dst_alpha, dst_alpha) dst_alpha
gl.ONE_MINUS_DST_ALPHA (1 - dst_alpha, 1 - dst_alpha, 1 - dst_alpha) 1 - dst_alpha
gl.CONSTANT_COLOR (constant_red, constant_green, constant_blue) constant_alpha
gl.ONE_MINUS_CONSTANT_COLOR (1 - constant_red, 1 - constant_green, 1 - constant_blue) 1 - constant_alpha
gl.CONSTANT_ALPHA (constant_alpha, constant_alpha, constant_alpha) constant_alpha
gl.ONE_MINUS_CONSTANT_ALPHA (1 - constant_alpha, 1 - constant_alpha, 1- constant_alpha) 1 - constant_alpha
gl.SRC_ALPHA_SATURATE a = min(src_alpha, 1 - dst_alpha); (a,a,a) 1.0

You set the blending factors in JavaScript using a call to blendFunc like this:

gl.blendFunc(enum src_factor, enum dst_factor);

For the factors that use a constant color, you set that color using this function:

void glBlendColor​(GLclampf red​, GLclampf green​, GLclampf blue​, GLclampf alpha​);

To complicate things further, you can also change the addition of the colors to subtraction using the blendEquation function. The three options are:

gl.blendEquation(gl.FUNC_ADD);
gl.blendEquation(gl.FUNC_SUBTRACT);
gl.blendEquation(gl.FUNC_REVERSE_SUBTRACT);

which makes the pipeline’s calculation be one of:

colorbuffer[x][y] = (colorbuffer[x][y] * dst_factor) + (color * src_factor);
colorbuffer[x][y] = (colorbuffer[x][y] * dst_factor) - (color * src_factor);
colorbuffer[x][y] = (color * src_factor) - (colorbuffer[x][y] * dst_factor);

To add even more complexity, you can separate the blending of the color components from the blending of the alpha values. If you use the functions:

gl.blendFunc(enum src_factor, enum dst_factor);
gl.blendEquation(enum equation_mode);

then the color components and the alpha values are treated the same way. If you use the functions:

gl.blendFuncSeparate(enum src_factor, enum dst_factor, enum src_alpha, enum dst_alpha);
gl.blendEquationSeparate(enum equation_rgb_mode, enum equation_alpha_mode);

then the color components and the alpha values are treated separately. All of these options can be very confusing, so let’s put it all in pseudocode to attempt to make it clearer. (Remember that this is implemented inside the graphics pipeline. You can’t change this implementation and you can’t implement this functionality in your fragment shader either.)

vec3 getColorFactor(mode, src_color, dst_color, constant_color) {
  switch (mode) {
    case gl.ZERO:                     factor = (0.0, 0.0, 0.0);
    case gl.ONE:                      factor = (1.0, 1.0, 1.0);
    case gl.SRC_COLOR:                factor = (    src_color[0],     src_color[1],     src_color[2]);
    case gl.ONE_MINUS_SRC_COLOR:      factor = (1.0-src_color[0], 1.0-src_color[1], 1.0-src_color[2]);
    case gl.DST_COLOR:                factor = (    dst_color[0],     dst_color[1],     dst_color[2]);
    case gl.ONE_MINUS_DST_COLOR:      factor = (1.0-dst_color[0], 1.0-dst_color[1], 1.0-dst_color[2]);
    case gl.SRC_ALPHA:                factor = (    src_color[3],     src_color[3],     src_color[3]);
    case gl.ONE_MINUS_SRC_ALPHA:      factor = (1.0-src_color[3], 1.0-src_color[3], 1.0-src_color[3]);
    case gl.DST_ALPHA:                factor = (    dst_color[3],     dst_color[3],     dst_color[3]);
    case gl.ONE_MINUS_DST_ALPHA:      factor = (1.0-dst_color[3], 1.0-dst_color[3], 1.0-dst_color[3]);
    case gl.CONSTANT_COLOR:           factor = (constant_color[0], constant_color[1], constant_color[2]);
    case gl.ONE_MINUS_CONSTANT_COLOR: factor = (1.0-constant_color[0], 1.0-constant_color[1], 1.0-constant_color[2]);
    case gl.CONSTANT_ALPHA:           factor = (constant_color[3], constant_color[3], constant_color[3]);
    case gl.ONE_MINUS_CONSTANT_ALPHA: factor = (1.0-constant_color[3], 1.0-constant_color[3], 1.0-constant_color[3]);
    case gl.SRC_ALPHA_SATURATE:       a = min(src_color[3], 1.0-dst_color[3]);
                                      factor = (a,a,);
  }
  return factor;
}

vec3 getAlphaFactor(mode, src_color, dst_color, constant_color) {
  switch (mode) {
    case gl.ZERO:               alpha_factor = 0.0;
    case gl.ONE                 alpha_factor = 1.0;
    case gl.SRC_COLOR           alpha_factor =     src_color[3];
    case gl.ONE_MINUS_SRC_COLOR alpha_factor = 1.0-src_color[3]);
    case gl.DST_COLOR           alpha_factor =     dst_color[3];
    case gl.ONE_MINUS_DST_COLOR alpha_factor = 1.0-dst_color[3];
    case gl.SRC_ALPHA           alpha_factor =     src_color[3];
    case gl.ONE_MINUS_SRC_ALPHA alpha_factor = 1.0-src_color[3];
    case gl.DST_ALPHA           alpha_factor =     dst_color[3];
    case gl.ONE_MINUS_DST_ALPHA alpha_factor = 1.0-dst_color[3];
    case gl.SRC_ALPHA_SATURATE  alpha_factor = 1.0;
  }
  return alpha_factor;
}

void renderPixel(x, y, z, color) {
  if (z < zbuffer[x][y]) {
    zbuffer[x][y] = z;

    dst_color = colorbuffer[x][y];
    src_color = color;

    dst_factor[0,1,2] = getColorFactor(dst_mode, src_color, dst_color, constant_color);
    dst_factor[3] = getAlphaFactor(dst_mode, src_color, dst_color, constant_color);

    src_factor[0,1,2] = getColorFactor(src_mode, src_color, dst_color, constant_color);
    src_factor[3] = getAlphaFactor(src_mode, src_color, dst_color, constant_color);

    switch (blendEquation) {
      case gl.FUNC_ADD:              dst_color = dst_color * dst_factor + src_color * src_factor;
      case gl.FUNC_SUBTRACT:         dst_color = dst_color * dst_factor - src_color * src_factor;
      case gl.FUNC_REVERSE_SUBTRACT: dst_color = src_color * src_factor - dst_color * dst_factor;
    }
    colorbuffer[x][y] = dst_color;
  }
}

Experimentation 4 (Alpha Blending Percentages)

Please experiment with the following WebGL demonstration program by selecting various combinations of blending factors.

Show: Code Canvas Run Info
313
 
1
/**
2
 * TransparencyExample4Render.js, By Wayne Brown, Spring 2016
3
 */
4
5
/**
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2015 C. Wayne Brown
9
 *
10
 * Permission is hereby granted, free of charge, to any person obtaining a copy
11
 * of this software and associated documentation files (the "Software"), to deal
12
 * in the Software without restriction, including without limitation the rights
13
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
 * copies of the Software, and to permit persons to whom the Software is
15
 * furnished to do so, subject to the following conditions:
16
 *
17
 * The above copyright notice and this permission notice shall be included in all
18
 * copies or substantial portions of the Software.
19
20
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
 * SOFTWARE.
27
 */
28
29
"use strict";
30
31
var console, Learn_webgl_point4;
32
33
//-------------------------------------------------------------------------
34
// Build, create, copy and render 3D objects specific to a particular
35
// model definition and particular WebGL shaders.
36
//-------------------------------------------------------------------------
37
window.TransparencyExample4Render = function (learn, vshaders_dictionary,
38
                                fshaders_dictionary, models, controls) {
39
40
  // Private variables
41
  var self = this;
42
  var canvas_id = learn.canvas_id;
43
  var out = learn.out;
44
45
  var gl = null;
46
  var program = null;
47
  var a_sphere = null;
48
49
  var number_spheres = 10;
50
  var number_transparent_spheres = 5;
51
  var number_opaque_spheres = number_spheres - number_transparent_spheres;
52
53
  var all_spheres = new Array(number_spheres);
54
55
  var matrix = new window.Learn_webgl_matrix();
56
  var transform = matrix.create();
57
  var view_model_transform = matrix.create();
58
  var projection = matrix.createPerspective(45.0, 1.0, 0.1, 100.0);
59
  var camera = matrix.create();
60
  var scale_matrix = matrix.create();
61
  var translate_matrix = matrix.create();
62
  var eye_x;
63
  var eye_y;
64
  var eye_z;
65
66
  // Public variables that will possibly be used or changed by event handlers.
67
  self.canvas = null;
68
  self.angle_x = 0.0;
69
  self.angle_y = 0.0;
70
  self.scale = 1.0;
71
  self.animate_active = false;
72
73
  // Light model
74
  var P4 = new window.Learn_webgl_point4();
75
  var V = new window.Learn_webgl_vector3();
76
  self.light_position = P4.create(10, 10, 10, 1);
77
  self.light_color = V.create(1, 1, 1); // white light
78
  self.shininess = 30;
79
  self.ambient_color = V.create(0.2, 0.2, 0.2); // low level white light
80
81
  var transformed_center = P4.create();
82
  var sort_indexes = null;
83
84
  self.src_factor = null;
85
  self.dst_factor = null;
86
87
  //-----------------------------------------------------------------------
88
  /**
89
   * Initialize the sort_indexes array for sorting the transparent models.
90
   * This array is re-sorted before each render of the scene.
91
   * @private
92
   */
93
  function _initialize_sorting() {
94
    var j;
95
96
    sort_indexes = new Array(number_transparent_spheres);
97
98
    for (j = 0; j < number_transparent_spheres; j += 1) {
99
      sort_indexes[j] = [j, 0.0];  // [index to triangle, distance from camera]
100
    }
101
  }
102
103
  /** ---------------------------------------------------------------------
104
   * Update the distance from each transparent sphere to the camera. This
105
   * uses the center of the sphere for the distance, which causes problems
106
   * if two transparent spheres overlap in 3D space.
107
   * @private
108
   */
109
  function _update_distances_to_camera () {
110
    var j, index, position;
111
112
    for (j = 0; j < number_transparent_spheres; j += 1) {
113
      index = sort_indexes[j][0];
114
115
      position = all_spheres[index].position;
116
      matrix.translate(translate_matrix, position[0], position[1], position[2]);
117
118
      // Combine the camera and sphere translation transforms.
119
      matrix.multiplySeries(view_model_transform, camera, translate_matrix);
120
121
      // Calculate where the sphere is in relationship to the camera.
122
      matrix.multiply(transformed_center, view_model_transform, position);
123
124
      // Set this model's distance from the camera
125
      sort_indexes[j][1] = transformed_center[2];
126
    }
127
  }
128
129
  /** ---------------------------------------------------------------------
130
   * Sort the models, back to front, based on their distance from the camera.
131
   * @private
132
   */
133
  function _sort_spheres () {
134
    var j, k, temp;
135
136
    // Perform an insertion sort on the models.
137
    for (j = 0; j < number_transparent_spheres; j += 1) {
138
      temp = sort_indexes[j];
139
      k = j - 1;
140
      while (k >= 0 && sort_indexes[k][1] > temp[1]) {
141
        sort_indexes[k + 1] = sort_indexes[k];
142
        k -= 1;
143
      }
144
      sort_indexes[k + 1] = temp;
145
    }
146
  }
147
148
  //-----------------------------------------------------------------------
149
  self.render = function () {
150
    var j, size, position, x_radians, y_radians, scale, index;
151
152
    // Clear the entire canvas window background with the clear color
153
    // out.display_info("Clearing the screen");
154
    gl.depthMask(true);
155
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
156
157
    // Build the camera transform to circle the origin.
158
    x_radians = matrix.toRadians(self.angle_x);
159
    y_radians = matrix.toRadians(self.angle_y);
160
    scale = Math.cos(x_radians);
161
    eye_x = Math.cos(y_radians) * scale * 20.0;
162
    eye_z = Math.sin(y_radians) * scale * 20.0;
163
    eye_y = Math.sin(x_radians) * 20.0;
164
    matrix.lookAt(camera, eye_x, eye_y, eye_z, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
165
166
    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
167
    // Set the location of the light source
168
    gl.uniform3f(program.u_Light_position, self.light_position[0],
169
        self.light_position[1],
170
        self.light_position[2]);
171
    gl.uniform3fv(program.u_Light_color, self.light_color);
172
    gl.uniform3fv(program.u_Ambient_color, self.ambient_color);
173
    gl.uniform1f(program.u_Shininess, self.shininess);
174
175
    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
176
    // Render the opaque spheres first.
177
    gl.disable(gl.BLEND);
178
179
    for (j = number_transparent_spheres; j < number_spheres; j += 1) {
180
      size = all_spheres[j].size;
181
      position = all_spheres[j].position;
182
183
      matrix.scale(scale_matrix, size, size, size);
184
      matrix.translate(translate_matrix, position[0], position[1], position[2]);
185
186
      // Combine the transforms into a single transformation
187
      matrix.multiplySeries(view_model_transform, camera,
188
                            translate_matrix, scale_matrix);
189
      matrix.multiplySeries(transform, projection, view_model_transform);
190
191
      // Assign a color to the sphere. Make it red if it is the selected object.
192
      a_sphere.setColor(all_spheres[j].color);
193
194
      a_sphere.render(transform, view_model_transform);
195
    }
196
197
    // Sort the transparent models, back to front, in reference to the camera
198
199
    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
200
    // Render the transparent spheres in sorted order.
201
202
    // Enable alpha blending and set the percentage blending factors
203
    gl.enable(gl.BLEND);
204
    gl.blendFunc(self.src_factor, self.dst_factor);
205
206
    // Turn off updating of the z-buffer
207
    gl.depthMask(false);
208
209
    // Update the distance of each sphere from the camera
210
    _update_distances_to_camera();
211
212
    // Sort the models based on their distance from the camera. The center
213
    // point of each sphere is used for the distance.
214
    _sort_spheres();
215
216
    // Render the transparent spheres
217
    for (j = 0; j < number_transparent_spheres; j += 1) {
218
      index = sort_indexes[j][0];
219
220
      size = all_spheres[index].size;
221
      position = all_spheres[index].position;
222
223
      matrix.scale(scale_matrix, size, size, size);
224
      matrix.translate(translate_matrix, position[0], position[1], position[2]);
225
226
      // Combine the transforms into a single transformation
227
      matrix.multiplySeries(view_model_transform, camera,
228
                            translate_matrix, scale_matrix);
229
      matrix.multiplySeries(transform, projection, view_model_transform);
230
231
      // Set the sphere's color.
232
      a_sphere.setColor(all_spheres[index].color);
233
234
      a_sphere.render(transform, view_model_transform);
235
    }
236
237
  };
238
239
  //-----------------------------------------------------------------------
240
  self.delete = function () {
241
242
    // Clean up shader programs
243
    gl.deleteShader(program.vShader);
244
    gl.deleteShader(program.fShader);
245
    gl.deleteProgram(program);
246
    program = null;
247
248
    // Delete each model's VOB
249
    a_sphere.delete();
250
251
    // Delete all of the events associated with this canvas
252
    events.removeAllEventHandlers();
253
254
    // Disable any animation
255
    self.animate_active = false;
256
  };
257
258
  //-----------------------------------------------------------------------
259
  // Object constructor. One-time initialization of the scene.
260
261
  // Get the rendering context for the canvas
262
  self.canvas = learn.getCanvas(canvas_id);
263
  if (self.canvas) {
264
    gl = learn.getWebglContext(self.canvas);
265
  }
266
  if (!gl) {
267
    return;
268
  }
269
270
  // Set up the rendering program and set the state of webgl
271
  program = learn.createProgram(gl, vshaders_dictionary.shader41, fshaders_dictionary.shader41);
272
273
  gl.useProgram(program);
274
275
  gl.enable(gl.DEPTH_TEST);
276
277
  gl.clearColor(0.9, 0.9, 0.9, 1.0);
278
279
  self.src_factor = gl.SRC_ALPHA;
280
  self.dst_factor = gl.ONE_MINUS_SRC_ALPHA;
281
282
  // Create Vertex Object Buffers for the models
283
  a_sphere = new window.Learn_webgl_model_render_41(gl, program,
284
                                          models.Icosphere, out);
285
286
  // Create a set of random positions, colors, and sizes for a group of spheres.
287
  var j, position_x, position_y, position_z, color;
288
  for (j = 0; j < number_spheres; j += 1) {
289
    position_x = Math.random() * 10 - 5.0;
290
    position_y = Math.random() * 10 - 5.0;
291
    position_z = Math.random() * 10 - 5.0;
292
    if (j < number_transparent_spheres) {
293
      color = new Float32Array([Math.random(), Math.random(), Math.random(), Math.random()]);
294
    } else {
295
      color = new Float32Array([Math.random(), Math.random(), Math.random(), 1.0]);
296
    }
297
    all_spheres[j] = { position: P4.create(position_x, position_y, position_z),
298
                       size: Math.random() * 2.5,
299
                       color: color
300
                     };
301
  }
302
303
  // Set up callbacks for user and timer events
304
  var events;
305
  events = new window.TransparencyExample4Events(self, controls, gl);
306
  events.animate();
307
308
  // Initialize the array of model indexes that will be used for sorting.
309
  _initialize_sorting();
310
};
311
312
313
./transparency_example4/transparency_example4.html

Alpha blending experimentation.

Please use a browser that supports "canvas"
Animate
Source blending factor
(Percentage of the object's color to render.)    
Destination blending factor
(Percentage of color already in the color buffer)
gl.ZERO
gl.ONE
gl.SRC_COLOR
gl.ONE_MINUS_SRC_COLOR
gl.DST_COLOR
gl.ONE_MINUS_DST_COLOR
gl.SRC_ALPHA
gl.ONE_MINUS_SRC_ALPHA
gl.DST_ALPHA
gl.ONE_MINUS_DST_ALPHA
gl.CONSTANT_COLOR
gl.ONE_MINUS_CONSTANT_COLOR
gl.CONSTANT_ALPHA
gl.ONE_MINUS_CONSTANT_ALPHA
gl.SRC_ALPHA_SATURATE
gl.ZERO
gl.ONE
gl.SRC_COLOR
gl.ONE_MINUS_SRC_COLOR
gl.DST_COLOR
gl.ONE_MINUS_DST_COLOR
gl.SRC_ALPHA
gl.ONE_MINUS_SRC_ALPHA
gl.DST_ALPHA
gl.ONE_MINUS_DST_ALPHA
gl.CONSTANT_COLOR
gl.ONE_MINUS_CONSTANT_COLOR
gl.CONSTANT_ALPHA
gl.ONE_MINUS_CONSTANT_ALPHA
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
Shader errors, Shader warnings, Javascript info
Shader '../lib/shaders/shader01.vert' has been downloaded.
In _initializeRendering: 1 of 5 files have been retrieved
Shader '../lib/shaders/shader01.frag' has been downloaded.
In _initializeRendering: 2 of 5 files have been retrieved
Shader '../lib/shaders/shader41.vert' has been downloaded.
In _initializeRendering: 3 of 5 files have been retrieved
Shader '../lib/shaders/shader41.frag' has been downloaded.
In _initializeRendering: 4 of 5 files have been retrieved
Model '../lib/models/sphere3.obj' has been downloaded.
Found these material MTL files: 0
In _initializeRendering: 5 of 6 files have been retrieved
Open this webgl program in a new tab or window

Summary

Simple visual effects related to transparency can be achieved using alpha blending. Accurate rendering of transparent models that intersect in 3D space requires a combination of techniques that involve the definition of the models, sorting, and configuration of the graphics pipeline. You would typically implement the minimum functionality needed to achieve the results your require for a particular scene.

Glossary

transparency
Some of the light that strikes an object passes through the object and surfaces behind the object are partially visible.
opaque
All light that strikes a surface is reflected. Opaque means no transparency.
insertion sort algorithm
The fastest, general purpose algorithm for sorting data that is already close to being sorted.
index sort
A set of data values is sorted without ever moving the data. The sort order is described as an array of indexes into the array that holds the data.
destination color
A color value stored in the color buffer of the frame buffer.
source color
A color value to be rendered for a surface.
alpha blending
The color of a pixel is calculated as a combination of two colors: the the destination color and the source color.
Next Section - 11.5 - Shadows