For this assignment we were tasked with producing two RSL pattern shaders - one that utilized S&T for coordinates and one that did not.


I read a great article at The Art of VFX about all of the screen replacements in The Girl with the Dragon Tattoo, which outlined the creation of a shader to emulate the effect of a filmed CRT or LCD screen, including all the subtle moire effects that go along. For this assignment I decided to implement a similar shader since I found the concept useful for my interests.

First, we should examine how images are displayed with CRT and LCD monitors. In both cases, the image is actually rendered as tiny red green and blue sub-pixels. In the case of old CRTs, the electron guns must shoot through a shadow mask, ensuring that each pixel had the correct and focused red, green and blue components. On the other hand, LCDs combine the separate red, green and blue elements without the need of a shadow mask.

The pattern was created with simple S&T tests, with the subpixel shape generalized to a rounded rectangle created with two spheres and a rectangular shape.

float rowRad = 0.116665;
float sOffsets[3] = {0.166665, 0.5, 0.833295};				
for(i = 0; i < 3; i+=1) {
	for(j = 0; j < 3; j+=2) {
		//generate spheres using sOffsets[j] as the tOffset
		//and rowRad as the radius
	//generate the rectangle for the center of each subpixel

Once the single pixel was created, I configured the tiling and set about pixelating the image to have a uniform value for each S&T 0-1 coordinate space, otherwise the image would simply look like a colored grid multiplied over the original image. The image is pixelated by expanding the texture space by the resolution size and then taking the floor value to create a number of discrete intervals corresponding to the resolution size.

float pixelatedS = floor(horizontalRes * s)+1;
float pixelatedT = floor(horizontalRes * tt)+1;
pixelatedS = pixelatedS / horizontalRes;
pixelatedT = pixelatedT / horizontalRes;
color pixelColor = texture(fullTexPath, pixelatedS, pixelatedT);

Next, the subpixel values were determined from the pixelated texture map and assigned as the color values for the red green and blue components of the subpixels.

The most problematic element of this assignment was anti-aliasing. The Nyquist Rate would require that I have twice the samples of my highest frequency signal to avoid aliasing. In this case, that would mean shading rates and pixel-sampling sizes that were astronomical, so I set about implementing some measure of filtering in my code. Currently, the shader uses smoothstep under user control to blur the edges of the subpixels. In most cases, some combination of a very fine shading rate and this smoothstep will generate the smooth images neccessary for serious use, and still maintains the minor aliasing one would expect from an actual filmed LCD or CRT display.

One particular issue I am encountering is the tiled S&T space with regard to aliasing. The small 1x1 tiles get quite blurry, but I still have very hard lines across the boundary of each S&T tile. I am currently unsure of how I might solve this problem. My current solution has been to render out quite large images and do the last part of filtering in compositing, where I'm able to generate high quality results quickly by testing the different filtering modes after a scale operation.

Overall, I am happy with how the shader turned out, but am still cautiously concerned about aliasing with all of these procedural pattern-style shaders.


For the second shader, I thought I might implement a Mandelbrot set as a shader. Even though I have some measure of a math background from my undergraduate, I'd never really taken the time to examine what the Mandelbrot set was or how it worked. The Mandelbrot set proved to only be the first bit of this shader exploration.

The Mandelbrot set is a set of complex numbers graphed in complex number space, with one axis for the real component and one for the imaginary component. Rather than post a large description of the math, I would direct those interested to the Wiki page.

I found the math fairly straightforward, and as this is a very common programming assignment, the popular algorithms are very well- defined online. I implemented a variant of the Escape Time Algorithm and achieved very good results right away.

Next, I expanded the shader to represent all 2D Julia Sets and support zooming. Unfortunately, I do not have the time or interest to set up any data types larger than floats, so zoom resolution is limited. Additionally there is a need for filtering to clean up the renders.

In Malcolm's programming class, we began very basic explorations with Volume Primitives to generate fog, and I found this to be a great opportunity to expand this shader to represent a 4D Julia Set. Again, I will skip the math part, as this is much more complicated than the previous Mandelbrot example. You can find more about the underlying concepts here. The basic idea is the same as the Mandelbrot, but now the complex number has three imaginary components.

As we cannot visualize four dimensions straight off, we must view 3d 'slices' of the 4D space. A 3D analogue of this concept is slicing through a sphere with a user-defined plane. The result is a 2 dimensional circle that exists in the coordinate space of the slice plane. In the case of the 4D set, we are taking a 3D 'slice' of the 4D space. If we iterate this 3D slice over time, we can consider the animation to be a representation of the 4D function, though this is hard to really conceptualize as anything beyond just an animated 3D fractal.

After writing a couple of utility functions to square a 4 dimensional function, I started getting interesting results. Half of the issue with fractals is finding the interesting parts, but I found some useful values online that generated beatiful images.

These functions are lit using a spotlight with a deep shadow, and the illuminance is collected in a 360 degree fashion (not Lambertian) with the following function of the surface shader.

illuminance(P) {
	//Cl is the return from the illuminance loop
	Ci = surfColor * Cl;

Opacity is not limited to a max of one, like most surface shaders. With Volume Primitives, opacity is treated more as extinction of light, and accumulated to infinity. I selected a value of 3 for each sub-voxel as larger values caused a very ugly flat grey effect.


While I am not sure it is worth exploring with fractals, I think it would be interesting to generate an isosurface of a volume shader. As Prof. Kesson has indicated, the biggest issues is going to be solving a surface normal, since we do not seem to have an ability to talk to nearby micropolygons to generate a surface normal. Several ideas are currently being considered involving point clouds or ray-marching to generate the surface normal.