830 likes | 849 Views
Explore advanced RGB color models, color addition, modulation, and light modulation techniques in computer graphics. Learn about representation, operations, and visualization in this detailed guide.
E N D
Lighting Models CIS 488/588 Bruce R. Maxim UM-Dearborn
Lighting • Huge topic in computer graphics • Nearly all 3D rendering packages make use of ray tracing to determine the shadows and reflective surfaces in the screen images • Software-based graphics engines cannot support the same complex lighting models that hardware (e.g. pixel and vertex shaders)
Color Models • Color can be represented using separate RGB values or using an index into a table • Some RGB models use integers 0-255 for each color and some use real numbers with values 0.0-1.0 for each • Float values are easier to work with mathematically, but integers can be faster for rendering since they can be written straight to the video buffer
Color Addition • When adding two RGB integer colors you need to catch overflow problems C1 = (r1, g1, b1) C2 = (r2, g2, b2) C = C1 + C2 C = (max(r1 + r2, 255), max(r1 + r2, 255), max(r1 + r2, 255))
Color Modulation • Color modulation is basically multiplication • Can be done using a real scalar (brightness modulation) or by multiplying the RGB values in a pairwise manner C1 = (r1, g1, b1) C2 = (r2, g2, b2) C = s * C1 = (s * r1, s *g1, s * b1) C = C1 * C2 = (r1 * r2, g1 * g2, b1 * b2) • Again, we need to beware of overflow
Demo8_1 // copy texture into temp for rendering and scaling Copy_Bitmap(&temp_text,0,0, &textures[curr_texture],0,0,TEXTSIZE, TEXTSIZE); /////////////////////////////////////////// // our little image processing algorithm :) //Cmodulated = s*C1 = (s*r1, s*g1, s*b1) USHORT *pbuffer = (USHORT *)temp_text.buffer;
Demo8_1 // perform RGB transformation on bitmap for (int iy = 0; iy < temp_text.height; iy++) for (int ix = 0; ix < temp_text.width; ix++) { USHORT pixel = pbuffer[iy*temp_text.width + ix]; int ri,gi,bi; // used to extract the rgb values _RGB565FROM16BIT(pixel, &ri,&gi,&bi); // perform scaling operation and test for overflow if ((ri = (float)ri * scalef) > 255) ri=255; if ((gi = (float)gi * scalef) > 255) gi=255; if ((bi = (float)bi * scalef) > 255) bi=255; // rebuild RGB and test for overflow and copy to buffer pbuffer[iy*temp_text.width + ix] = _RGB16BIT565(ri,gi,bi); } // end for ix // draw texture Draw_Bitmap16(&temp_text, back_buffer, back_lpitch,0);
Light Modulation • Pixel Shading plus Modulation (Demo8_2) Pixel_dest[x][y]rgb = pixel_source[x][y]rgb * ambient + pixel_source[x][y]rgb * light_map[x][y]rgb • Pixel Shading plus Modulation (Demo8_2b) Pixel_dest[x][y]rgb = pixel_source[x][y]rgb * ambient + light_map[x][y]rgb • Please note: these are done separately for each RGB value
Demo8_2 // Pixel_dest[x,y]rgb = pixel_source[x,y]rgb * ambient + // pixel_source[x,y]rgb * light_map[x,y]rgb USHORT *sbuffer = (USHORT *)textures[curr_texture].buffer; USHORT *lbuffer = (USHORT *)lightmaps[curr_lightmap].buffer; USHORT *tbuffer = (USHORT *)temp_text.buffer; // perform RGB transformation on bitmap for (int iy = 0; iy < temp_text.height; iy++) for (int ix = 0; ix < temp_text.width; ix++) { int rs,gs,bs; // used to extract the source rgb values int rl, gl, bl; // light map rgb values int rf,gf,bf; // the final rgb terms // extract pixel from source bitmap USHORT spixel = sbuffer[iy*temp_text.width + ix];
Demo8_2 // extract RGB value _RGB565FROM16BIT(spixel, &rs,&gs,&bs); // extract pixel from lightmap bitmap USHORT lpixel = lbuffer[iy*temp_text.width + ix]; // extract RGB values _RGB565FROM16BIT(lpixel, &rl,&gl,&bl); // ambient base texture term + modulation term rf = ( scalef * (float)rs ) + ( (float)rs*(float)rl/(float)64 ); gf = ( scalef * (float)gs ) + ( (float)gs*(float)rl/(float)64 ); bf = ( scalef * (float)bs ) + ( (float)bs*(float)rl/(float)64 );
Demo8_2 // test for overflow if (rf > 255) rf=255; if (gf > 255) gf=255; if (bf > 255) bf=255; // rebuild RGB and test for overflow and write back to buffer tbuffer[iy*temp_text.width + ix] = _RGB16BIT565(rf,gf,bf); } // end for ix // draw textures Draw_Bitmap16(&temp_text, back_buffer, back_lpitch,0); Draw_Bitmap16 (&textures[curr_texture], back_buffer, back_lpitch,0); Draw_Bitmap16 (&lightmaps[curr_lightmap], back_buffer, back_lpitch,0);
Demo8_2b // Pixel_dest[x,y]rgb = pixel_source[x,y]rgb * ambient + // light_map[x,y]rgb USHORT *sbuffer = (USHORT *)textures[curr_texture].buffer; USHORT *lbuffer = (USHORT *)lightmaps[curr_lightmap].buffer; USHORT *tbuffer = (USHORT *)temp_text.buffer; // perform RGB transformation on bitmap for (int iy = 0; iy < temp_text.height; iy++) for (int ix = 0; ix < temp_text.width; ix++) { int rs,gs,bs; // used to extract the source rgb values int rl, gl, bl; // light map rgb values int rf,gf,bf; // the final rgb terms // extract pixel from source bitmap USHORT spixel = sbuffer[iy*temp_text.width + ix];
Demo8_2b // extract RGB values _RGB565FROM16BIT(spixel, &rs,&gs,&bs); // extract pixel from lightmap bitmap USHORT lpixel = lbuffer[iy*temp_text.width + ix]; // extract RGB values _RGB565FROM16BIT(lpixel, &rl,&gl,&bl); // simple formula base + scale * lightmap rf = ( scalef * (float)rl ) + ( (float)rs ); gf = ( scalef * (float)gl ) + ( (float)gs ); bf = ( scalef * (float)bl ) + ( (float)bs ); // test for overflow if (rf > 255) rf=255; if (gf > 255) gf=255; if (bf > 255) bf=255;
Demo8_2b // rebuild RGB and test for overflow and write back to buffer tbuffer[iy*temp_text.width + ix] = _RGB16BIT565(rf,gf,bf); } // end for ix // draw textures Draw_Bitmap16(&temp_text, back_buffer, back_lpitch,0); Draw_Bitmap16 (&textures[curr_texture], back_buffer, back_lpitch,0); Draw_Bitmap16 (&lightmaps[curr_lightmap], back_buffer, back_lpitch,0);
Alpha Blending • Process of adding two or more pixel colors together to arrive at a destination pixel • Blending means that percentages of each source are used to arrive a final pixel • Used to simulate transparency and shadows • Basic Two-Source Alpha Blending Pixel_dest[x][y]rgb = alpha * pixel_source1[x][y]rgb + (1 – alpha) * pixel_source2[x][y]rgb
Demo8_3 //Pixel_dest[x][y]rgb = alpha * pixel_source1[x][y]rgb + // (1-alpha)* pixel_source2[x][y]rgb USHORT *s1buffer = (USHORT *)textures1[curr_texture1].buffer; USHORT *s2buffer = (USHORT *)textures2[curr_texture2].buffer; USHORT *tbuffer = (USHORT *)temp_text.buffer; // perform RGB transformation on bitmap for (int iy = 0; iy < temp_text.height; iy++) for (int ix = 0; ix < temp_text.width; ix++) { int rs1,gs1,bs1; // used to extract the source rgb values int rs2, gs2, bs2; // light map rgb values int rf,gf,bf; // the final rgb terms // extract pixel from source bitmap USHORT s1pixel = s1buffer[iy*temp_text.width + ix];
Demo8_3 // extract RGB values _RGB565FROM16BIT(s1pixel, &rs1,&gs1,&bs1); // extract pixel from lightmap bitmap USHORT s2pixel = s2buffer[iy*temp_text.width + ix]; // extract RGB values _RGB565FROM16BIT(s2pixel, &rs2,&gs2,&bs2); // alpha blend them rf = (alphaf * (float)rs1 ) + ((1 - alphaf) * (float)rs2 ); gf = (alphaf * (float)gs1 ) + ((1 - alphaf) * (float)gs2 ); bf = (alphaf * (float)bs1 ) + ((1 - alphaf) * (float)bs2 ); // test for overflow if (rf > 255) rf=255; if (gf > 255) gf=255; if (bf > 255) bf=255;
Demo8_3 // rebuild RGB and test for overflow copy to buffer tbuffer[iy*temp_text.width + ix] = _RGB16BIT565(rf,gf,bf); } // end for ix // draw textures Draw_Bitmap16(&temp_text, back_buffer, back_lpitch,0); Draw_Bitmap16 (&textures1[curr_texture1], back_buffer, back_lpitch,0); Draw_Bitmap16 (&textures2[curr_texture2], back_buffer, back_lpitch,0);
Ambient Light • This is the light that is all around us with no specific light source • Ambient lighting can be modeled using an additive tem that is the product of and ambient reflective surface color and the ambient light intensity (use floating point RGB values) Itotala = Csambient * Iambient
Diffuse Light - 1 • The light that is scattered off an object due to a light source and the reflectivity of the surface itself • Diffuse light is irrelevant to viewer position • Intensity of Diffuse Light as a Function of Angle Between Surface Normal and Incident Light Source Vectors I = (n•l) = cos
Diffuse Lighting Models • Single Source Diffuse Itotald = Rsdiffuse * Idiffuse * (n• l) • Single-Source Ambient and Diffuse Itotalad = Rsdiffuse * Idiffuse + [Rsdiffuse * Idiffuse * (n• l)] • Ambient and Diffuse for Multiple Sources Itotalad = Rsdiffuse * Idiffuse + Rsdiffuse* [I(i)diffuse * (ni• li)]
Specular Lighting • Objects the seem to be shiny reflections of the light sources themselves • Caused by lots of microfacets on the surface have the same orientations • Related to the light source position (l), surface normal (n), viewer position (v), and reflection (r) vectors
Specular Lighting Models • Single Source Specular Itotals = Rsspecular * Ispecular * max(r• v, 0)sp • Single-Source Specular with Source Test Itotals = Rsspecular * Ispecular * max(r• v, 0)sp * [(n • l) > 0 ? 1:0] • Specular for Multiple Sources Itotals = Rsspecular * [I(i)specular * max(ri• vi, 0)sp * [(ni • li) > 0 ? 1:0]
Emissive Lighting • Easiest lighting model to implement • It is the surface self-illumination • Could almost be thought of as a light source that does not illuminate other objects Itotale = Rsemission
Total Lighting Equation • Single Pixel with Multiple Light Sources Itotalaeds = Rsambient * Iambient * Rsemission + Rsdiffuse* [I(i)diffuse * (ni• li)] + Rsspecular * [I(i)specular * max(ri• vi, 0)sp * [(ni • li) > 0 ? 1:0]
Directional Lights • Lights so far away that all the light rays that strike the surface are parallel (also known as infinite lights) • The intensity is always the same as the initial intensity and color independent of distance I(d)dir = I0dir * Cldir
Point Lights • Modeled as single points in 3D space • Falloff (attenuation) based on distance from light is modeled with constants (kc, kl, kq) • Model is (kq can be set to zero) d = |p – s| p = point light source s = point on surface I(d)point = I0point * Clpoint / (kc + kl * d + kq * d2)
Spotlights • Extremely expensive computationally • Spotlight at position p, with direction l, strikes surface point s • The angle to define region known as the umbra and angle to define the region known as the penumbra
Spotlight – Cases 1 & 2 • Case 1: If > then don’t light the point I(d)spotlight = 0 • Case 2: If > then inside umbra d = |p – s| I(d)spotlight = I0spotlight * Clspotlight / (kc + kl * d + kq * d2)
Spotlight – Case 3 • Case 3: If /2 < < /2 then outside umbra and inside penumbra d = |p – s| pf = power factor I(d)spotlight = I0spotlight * Clspotlight * max((l • s),0)pf/ (kc + kl * d + kq * d2) • Can be simplified to I(d)spotlight = I0spotlight * Clspotlight / (kc + kl * d + kq * d2) * (cos - cos /2 )pf / (cos /2 - cos /2 )
New 3D Pipeline • Object • Local to World • Object Removal • Back-face Removal • Lighting • World to Camera • Camera to Perspective • Perspective to Screen • Rasterize
Support for Materials • PLG/PLX format does not have any support for materials (textures) • The graphics engine needed to have a number of materials that can applied to polygons during rendering by looking up a material ID or pointer to use access the material
Initializing Material Library int Reset_Materials_MATV1(void) { // this function resets all the materials static int first_time = 1; // if this is the first time then zero EVERYTHING out if (first_time) { memset(materials, 0, MAX_MATERIALS*sizeof(MATV1)); first_time = 0; } // end if
Initializing Material Library // scan thru materials and release all textures, if any? for (int curr_matt = 0; curr_matt < MAX_MATERIALS; curr_matt++) { // regardless if the material is active check to see if there // is a dangling texture map Destroy_Bitmap(&materials[curr_matt].texture); // now it's safe to zero memory out memset(&materials[curr_matt], 0, sizeof(MATV1)); } // end if return(1); } // end Reset_Materials_MATV1 • Functions to access the material library will come later
Initializing All Lights to Off int Reset_Lights_LIGHTV1(void) { // function simply resets all lights in the system static int first_time = 1; memset(lights, 0, MAX_LIGHTS*sizeof(LIGHTV1)); // reset number of lights num_lights = 0; // reset first time first_time = 0; // return success return(1); } // end Reset_Lights_LIGHTV1
Creating Lights int Init_Light_LIGHTV1(int index,// index to (0..MAX_LIGHTS-1) int _state, // state of light int _attr, // type of light RGBAV1 _c_ambient, // ambient light intensity RGBAV1 _c_diffuse, // diffuse light intensity RGBAV1 _c_specular, // specular light intensity POINT4D_PTR _pos, // position of light VECTOR4D_PTR _dir, // direction of light float _kc, // attenuation factors float _kl, float _kq, float _spot_inner, // inner angle for spotlight float _spot_outer, // outer angle for spotlight float _pf) // power factor spotlights
Creating Lights { // this function initializes a light based on the flags sent // in _attr, values thataren't needed are set to 0 by caller // make sure light is in range if (index < 0 || index >= MAX_LIGHTS)return(0); // all good, initialize the light (many fields may be dead) lights[index].state = _state; // state of light lights[index].id = index; // id of light lights[index].attr = _attr; // type of light lights[index].c_ambient = _c_ambient; // ambient light lights[index].c_diffuse = _c_diffuse; // diffuse light lights[index].c_specular = _c_specular; // specular light lights[index].kc = _kc; // attenuation lights[index].kl = _kl; lights[index].kq = _kq;
Creating Lights if (_pos) VECTOR4D_COPY(&lights[index].pos, _pos); if (_dir) { VECTOR4D_COPY(&lights[index].dir, _dir); // normalize it VECTOR4D_Normalize(&lights[index].dir); } // end if lights[index].spot_inner = _spot_inner; // inner angle spot lights[index].spot_outer = _spot_outer; // outer angle spot lights[index].pf = _pf; // power factor // return light index as success return(index); } // end Create_Light_LIGHTV1
Creating Ambient Light ambient_light = Init_Light_LIGHTV1(0, // 0 for ambient LIGHTV1_STATE_ON, // turn on LIGHTV1_ATTR_AMBIENT, // light type _RGBA32BOT(255,255,255,0), // pure white 0, 0, NULL, NULL, // NA for ambient 0, 0, 0, 0, 0, 0); • Once created changing state can be done globally Lights[ambient_light].state = LIGHTV1_STATE_OFF;
Creating Directional Light VECTOR4D sun_dir = {0, -1, 0, 0}; sun_light = Init_Light_LIGHTV1(1, // 1 for sunlight LIGHTV1_STATE_ON, // turn on LIGHTV1_ATTR_INFINITE, // light type 0, _RGBA32BOT(255,255,0 ,0), // pure yellow 0, NULL, &sun_dir, // dir = (0,-1,0) 0, 0, 0, 0, 0, 0); // NA
Creating Point Light VECTOR4D sun_pos = {0, 10000, 0, 0}; sun_light = Init_Light_LIGHTV1(1, // 1 for sunlight LIGHTV1_STATE_ON, // turn on LIGHTV1_ATTR_POINT, // light type 0, _RGBA32BOT(255,255,0 ,0), // pure yellow 0, &sun_pos, NULL, // pos on y-axis 0.0f, 0.001f, 0.0f, // linear atten 0.0f, 0.0f, 0.0f); // NA
Creating Spotlight VECTOR4D spot_pos = {1000, 1000, -1000, 0}; VECTOR4D spot_dir = {-1, -1, 1, 0}; float umbra= 30; float penumbra = 60; falloff = 1.0; sun_light = Init_Light_LIGHTV1(5, // 5 for spot LIGHTV1_STATE_ON, // turn on LIGHTV1_SPOTLIGHT, // light type 0, _RGBA32BOT(128,128,128,0), // 50% white 0, &sun_pos, &sun_dir, // pos & dir 0.0f, 0.0f, 0.0f, // NA umbra, penumbra, falloff); // spot info
Shading • 8-bit modes are 3 times faster than 16-bit • 16-bit modes are easier to track since there are 1:1 mapping of intensities in RGB space to pixels • If you have blue material and red ambient light nothing will be visible in RGB space (0,0,255) * (1.0,0,0) = (0,0,0) • Easier to assume white ambient light (0,0,255) * (1.0,1.0,1.0) = (0,0,255)
Indexed Mode Lighting • We to convert RGB values into color indices and then write those values to the screen • We will need to do a closest-match scan of the color pallet and select the one that comes closest (similar to hashing/lookup) • As a good compromise use a 16-bit look up table (0-65535) translate 16-bit to 8-bit color pallet (0-255) final_pixel_index = rgblookup[final_pixelrgb16];
Simplified Intensity Model • The expensive part of shading is calculation of the final RGB value • If we ignore light color and material color we can simplify the model a great deal • We can create a a table that has 256 rows (one for each pallet color) and each row has 256 entries (the best matches for each intensity value) • We write a shader translation lookup is Rgbintensityloopup[color][intensity]
Color Intensity Algorithm - 1 Given: color c = RGB(50,20,100) c* = RGB(red*,green*,blue*) Step 1: Compute largest component value for this example blue = 100 Step 2: Set maximum component to 255 and compute ratio blue=100, blue* = 255, ratio = 255/100 = 2.55
Color Intensity Algorithm - 2 Step 3: scale all other channels by ratio red* = red * ratio = 50 * 2.55 = 127.5 green* = green * ratio = 20 * 2.55 = 51.0 blue*= blue * ratio = 100 * 2.55 = 255 c* = RGB(128,51,255)
Indexed_Intensity_Table_Builder // these look up tables are used by the 8-bit lighting engine // the first one holds a color translation table in the form of each // row is a color 0..255, and each row consists of 256 shades of that // color the data in each row is the color/intensity indices and the // resulting value is an 8-bit index into the real color lookup that // should be used as the color // the second table works by each index being a compressed 16bit RG // value the data indexed by that RGB value IS the index 0..255 of // the real color lookup that matches the desired color the closest UCHAR rgbilookup[256][256]; // intensity RGB 8-bit lookup storage UCHAR rgblookup[65536]; // RGB 8-bit color lookup
Indexed_Intensity_Table_Builder int RGB_16_8_Indexed_Intensity_Table_Builder (LPPALETTEENTRY src_palette, // source palette UCHAR rgbilookup[256][256], // lookup table int intensity_normalization) // this function takes the source palette to compute the intensity // shading table withthe table will be formatted such that each row // is a color index, and each columnis the shade 0..255 desired, the // output is a single byte indexin either case, it's up to the caller // to send in the rgbilookup table pre-allocated64k buffer byte // [256][256]the function doesn't allocate memory for the caller // the function builds the table by looping thru each color in the // color palette and thenfor each color, it scales the color to // maximum intensity without overflow the RGB channels and then uses // this as the 100% intensity value of the color, then the algorithm // computesthe 256 shades of the color, and then uses the standard // least squares scan the find the colors in the palette and stores // them in the row of the current color under intensity translation