Demo ogl_multitexture_blending
Demo 11 of 19 in category CodeSampler
 |
#-----------------------------------------------------------------------------
# Name: ogl_multitexture_blending.cpp
# Author: Kevin Harris (kevin@codesampler.com)
# Last Modified: 02/08/05
# Description: This sample demonstrates how to use the OpenGL extensions
# GL_ARB_multitexture and GL_ARB_texture_env_combine in
# conjunction with specially encoded vertex colors to blend
# three textures together.
#
# This technique is very popular in terrain rendering engines
# which use it to blend dramatically different textures such
# as rock and grass together with out creating a noticeable
# edge. For example, with three textures consisting of stone,
# grass, and sand you can render a mountain that blends in
# patches of grass and sand at its base.
#
# Of course, while this technique remains popular as a
# fall-back for older hardware, shaders make this task a lot
# easier and are quickly becoming the preferred method for
# terrain texture blending.
#
# The technique basically consists of the following steps:
#
# Step 1: Take the desired contribution of the three textures and encode them
# into the vertex's color such that the RGB portion of the color
# controls the interpolation between texture stages 0 and 1, and the
# color's ALPHA controls the interpolation between texture stages
# 1 and 2.
#
# Step 2: Use GL_ARB_multitexture to apply three textures simultaneously to
# our geometry.
#
# Step 3: Set the first texture on texture stage 0.
#
# Step 4: During texture stage 1, use GL_INTERPOLATE_ARB to linearly
# interpolate between the output of stage 0 and the texture of stage 1
# with GL_SRC_COLOR (i.e. the RGB part of the color).
#
# Step 4: During texture stage 2, use GL_INTERPOLATE_ARB to linearly
# interpolate between the output of stage 1 and the texture of stage 2
# with GL_SRC_ALPHA (i.e. the ALPHA part of the color).
#
# Control Keys: F1 - Increase contribution of texture 0
# F2 - Decrease contribution of texture 0
# F3 - Increase contribution of texture 2
# F4 - Decrease contribution of texture 2
# F5 - Toggle wire-frame mode.
# Up - View moves forward
# Down - View moves backward
#
# Note: I tried to create an intuitive way to set the contribution of each
# texture at run-time using the function keys, but this system is still
# a little confusing since I only allow the contribution of texture 0
# and texture 2 to be adjusted. This is due to the fact that the
# equation for encoding the blending info into the vertex color simply
# infers the contribution value of texture 1 based on the values for
# textures 0 and 2. Therefore, the contribution value of texture 1 must
# be indirectly set by adjusting the contributions of textures 0 and 2.
#-----------------------------------------------------------------------------
#
# Original C++ code by Kevin Harris (kevin@codesampler.com)
# See www.codesampler.com for the original files
# OpenGL samples page 4: Multi-Texture Blending
#
# Modified for Tcl3D by Paul Obermeier 2007/03/10
# See www.tcl3d.org for the Tcl3D extension.
package require Tk
package require Img
package require tcl3d
# Font to be used in the Tk listbox.
set g_listFont {-family {Courier} -size 10}
set g_WinWidth 640
set g_WinHeight 480
set g_LastMousePosX(1) 0
set g_LastMousePosY(1) 0
set g_fSpinX(1) 0.0
set g_fSpinY(1) 0.0
set g_fDistance -4.0
set g_bWireFrameMode false
set g_fContributionOfTex0 0.33
set g_fContributionOfTex1 0.33
set g_fContributionOfTex2 0.33
# Determine the directory of this script.
set g_scriptDir [file dirname [info script]]
# Show errors occuring in the Togl callbacks.
proc bgerror { msg } {
tk_messageBox -icon error -type ok -message "Error: $msg\n\n$::errorInfo"
exit
}
# Print info message into widget a the bottom of the window.
proc PrintInfo { msg } {
if { [winfo exists .fr.info] } {
.fr.info configure -text $msg
}
}
proc SetMouseInput { btn x y } {
set ::g_LastMousePosX($btn) $x
set ::g_LastMousePosY($btn) $y
}
proc GetMouseInput { btn x y } {
set nXDiff [expr ($x - $::g_LastMousePosX($btn))]
set nYDiff [expr ($y - $::g_LastMousePosY($btn))]
set ::g_fSpinX($btn) [expr $::g_fSpinX($btn) - $nXDiff]
set ::g_fSpinY($btn) [expr $::g_fSpinY($btn) - $nYDiff]
set ::g_LastMousePosX($btn) $x
set ::g_LastMousePosY($btn) $y
.fr.toglwin postredisplay
}
proc SetContributionOfTex0 { val } {
global g_fContributionOfTex0 g_fContributionOfTex2
set g_fContributionOfTex0 [expr { $g_fContributionOfTex0 + $val }]
if { $g_fContributionOfTex0 > 1.0 } {
set g_fContributionOfTex0 1.0
} elseif { $g_fContributionOfTex0 < 0.0 } {
set g_fContributionOfTex0 0.0
}
# If the total contribution of textures 0 and 2 is
# greater than 1.0f after we increased the
# contribution of texture 0, we need to reduce the
# contribution from texture 2 to balance it out.
while { ($g_fContributionOfTex0 + $g_fContributionOfTex2) > 1.0 } {
set g_fContributionOfTex2 [expr { $g_fContributionOfTex2 - 0.01 }]
}
if { $g_fContributionOfTex2 < 0.0 } {
set g_fContributionOfTex2 0.0
}
.fr.toglwin postredisplay
}
proc SetContributionOfTex2 { val } {
global g_fContributionOfTex0 g_fContributionOfTex2
set g_fContributionOfTex2 [expr { $g_fContributionOfTex2 + $val }]
if { $g_fContributionOfTex2 > 1.0 } {
set g_fContributionOfTex2 1.0
} elseif { $g_fContributionOfTex2 < 0.0 } {
set g_fContributionOfTex2 0.0
}
# If the total contribution of textures 0 and 2 is
# greater than 1.0f after we increased the
# contribution of texture 2, we need to reduce the
# contribution from texture 0 to balance it out.
while { ($g_fContributionOfTex0 + $g_fContributionOfTex2) > 1.0 } {
set g_fContributionOfTex0 [expr { $g_fContributionOfTex0 - 0.01 }]
}
if { $g_fContributionOfTex0 < 0.0 } {
set g_fContributionOfTex0 0.0
}
.fr.toglwin postredisplay
}
proc ToggleWireFrameMode {} {
set ::g_bWireFrameMode [expr ! $::g_bWireFrameMode]
.fr.toglwin postredisplay
}
proc SetDistance { val } {
set ::g_fDistance [expr $::g_fDistance + $val]
.fr.toglwin postredisplay
}
proc LoadTexture { name index } {
set texName [file join $::g_scriptDir $name]
set retVal [catch {set phImg [image create photo -file $texName]} err1]
if { $retVal != 0 } {
error "Error reading image $texName ($err1)"
} else {
set w [image width $phImg]
set h [image height $phImg]
set n [tcl3dPhotoChans $phImg]
set pTextureImage [tcl3dVectorFromPhoto $phImg]
image delete $phImg
}
set ::g_textureID($index) [tcl3dVector GLuint 1]
glGenTextures 1 $::g_textureID($index)
glBindTexture GL_TEXTURE_2D [$::g_textureID($index) get 0]
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MIN_FILTER $::GL_LINEAR
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MAG_FILTER $::GL_LINEAR
glTexImage2D GL_TEXTURE_2D 0 3 $w $h 0 $::GL_RGBA GL_UNSIGNED_BYTE $pTextureImage
$pTextureImage delete
}
#-----------------------------------------------------------------------------
# Name: BeginRenderText
# Desc: Utility function for using the bitmap-based character fonts defined
# above. Call this function to begin rendering text. Call the function
# EndRenderText to stop.
#-----------------------------------------------------------------------------
proc BeginRenderText { toglwin nWindowWidth nWindowHeight } {
set ::FontBase [$toglwin loadbitmapfont]
# Push back and cache the current state of pixel alignment.
glPushClientAttrib GL_CLIENT_PIXEL_STORE_BIT
glPixelStorei GL_UNPACK_SWAP_BYTES $::GL_FALSE
glPixelStorei GL_UNPACK_LSB_FIRST $::GL_FALSE
glPixelStorei GL_UNPACK_ROW_LENGTH 0
glPixelStorei GL_UNPACK_SKIP_ROWS 0
glPixelStorei GL_UNPACK_SKIP_PIXELS 0
glPixelStorei GL_UNPACK_ALIGNMENT 1
# Push back and cache the current state of depth testing and lighting
# and then disable them.
glPushAttrib [expr $::GL_TEXTURE_BIT | $::GL_DEPTH_TEST | $::GL_LIGHTING]
glDisable GL_TEXTURE_2D
glDisable GL_DEPTH_TEST
glDisable GL_LIGHTING
# Push back the current matrices and go orthographic for text rendering.
glMatrixMode GL_PROJECTION
glPushMatrix
glLoadIdentity
glOrtho 0 $nWindowWidth $nWindowHeight 0 -1 1
glMatrixMode GL_MODELVIEW
glPushMatrix
glLoadIdentity
}
#-----------------------------------------------------------------------------
# Name: EndRenderText
# Desc: Utility function for using the bitmap-based character fonts defined
# above. Call this function to stop rendering text. The call to
# BeginRenderText should come first and be paired with this function.
#-----------------------------------------------------------------------------
proc EndRenderText { toglwin } {
$toglwin unloadbitmapfont $::FontBase
unset ::FontBase
# Pop everything back to what ever it was set to before we started
# rendering text to the screen.
glMatrixMode GL_PROJECTION
glPopMatrix
glMatrixMode GL_MODELVIEW
glPopMatrix
glPopClientAttrib
glPopAttrib
}
#-----------------------------------------------------------------------------
# Name: RenderText
# Desc: Utility function for using the bitmap-based character fonts defined
# above. This function must be called in between a call to
# BeginRenderText and EndRenderText. See the example below:
#
# BeginRenderText $toglwin $nWindowWidth $nWindowHeight
# RenderText 5 10 "This is a test!"
# EndRenderText $toglwin
#
#-----------------------------------------------------------------------------
proc RenderText { x y str } {
set len [string length $str]
if { $len > 0 } {
glRasterPos2f $x $y
glListBase $::FontBase
set sa [tcl3dVectorFromString GLubyte $str]
glCallLists $len GL_UNSIGNED_BYTE $sa
$sa delete
}
}
proc checkExtProc { extProc } {
if { ![tcl3dOglHaveFunc $extProc] } {
error "Extension proc $extProc not available"
}
}
proc CreateCallback { toglwin } {
if { 1 } {
LoadTexture "texture0.bmp" 0
LoadTexture "texture1.bmp" 1
LoadTexture "texture2.bmp" 2
} else {
LoadTexture "test_texture0.bmp" 0
LoadTexture "test_texture1.bmp" 1
LoadTexture "test_texture2.bmp" 2
}
glClearColor 0.0 0.0 0.0 1.0
glEnable GL_TEXTURE_2D
glMatrixMode GL_PROJECTION
glLoadIdentity
gluPerspective 45.0 [expr double($::g_WinWidth)/double($::g_WinHeight)] \
0.1 100.0
# Check for GL_ARB_multitexture extension.
if { ![tcl3dOglHaveExtension $toglwin "GL_ARB_multitexture"] } {
error "Extension GL_ARB_multitexture missing"
}
checkExtProc "glActiveTextureARB"
checkExtProc "glMultiTexCoord2fARB"
checkExtProc "glClientActiveTextureARB"
}
proc ReshapeCallback { toglwin { w -1 } { h -1 } } {
set w [$toglwin width]
set h [$toglwin height]
glViewport 0 0 $w $h
glMatrixMode GL_PROJECTION
glLoadIdentity
gluPerspective 45.0 [expr double($w)/double($h)] 0.1 100.0
}
proc DisplayCallback { toglwin } {
global g_fContributionOfTex0 g_fContributionOfTex1 g_fContributionOfTex2
glClear [expr $::GL_COLOR_BUFFER_BIT | $::GL_DEPTH_BUFFER_BIT]
# Viewport command is not really needed, but has been inserted for
# Mac OSX. Presentation framework (Tk) does not send a reshape event,
# when switching from one demo to another.
glViewport 0 0 [$toglwin width] [$toglwin height]
glMatrixMode GL_MODELVIEW
glLoadIdentity
glTranslatef 0.0 0.0 $::g_fDistance
glRotatef [expr -1.0 * $::g_fSpinY(1)] 1.0 0.0 0.0
glRotatef [expr -1.0 * $::g_fSpinX(1)] 0.0 1.0 0.0
if { $::g_bWireFrameMode } {
glPolygonMode GL_FRONT_AND_BACK GL_LINE
} else {
glPolygonMode GL_FRONT_AND_BACK GL_FILL
}
# Texture Stage 0
# Simply output texture0 for stage 0.
glActiveTextureARB GL_TEXTURE0_ARB
glEnable GL_TEXTURE_2D
glBindTexture GL_TEXTURE_2D [$::g_textureID(0) get 0]
glTexEnvi GL_TEXTURE_ENV GL_TEXTURE_ENV_MODE $::GL_REPLACE
# Texture Stage 1
# Perform a linear interpolation between the output of stage 0
# (i.e texture0) and texture1 and use the RGB portion of the vertex's
# color to mix the two.
glActiveTextureARB GL_TEXTURE1_ARB
glEnable GL_TEXTURE_2D
glBindTexture GL_TEXTURE_2D [$::g_textureID(1) get 0]
glTexEnvi GL_TEXTURE_ENV GL_TEXTURE_ENV_MODE $::GL_COMBINE_ARB
glTexEnvi GL_TEXTURE_ENV GL_COMBINE_RGB_ARB $::GL_INTERPOLATE_ARB
glTexEnvi GL_TEXTURE_ENV GL_SOURCE0_RGB_ARB $::GL_PREVIOUS_ARB
glTexEnvi GL_TEXTURE_ENV GL_OPERAND0_RGB_ARB $::GL_SRC_COLOR
glTexEnvi GL_TEXTURE_ENV GL_SOURCE1_RGB_ARB $::GL_TEXTURE
glTexEnvi GL_TEXTURE_ENV GL_OPERAND1_RGB_ARB $::GL_SRC_COLOR
glTexEnvi GL_TEXTURE_ENV GL_SOURCE2_RGB_ARB $::GL_PRIMARY_COLOR_ARB
glTexEnvi GL_TEXTURE_ENV GL_OPERAND2_RGB_ARB $::GL_SRC_COLOR
# Texture Stage 2
# Perform a linear interpolation between the output of stage 1
# (i.e texture0 mixed with texture1) and texture2 and use the ALPHA
# portion of the vertex's color to mix the two.
glActiveTextureARB GL_TEXTURE2_ARB
glEnable GL_TEXTURE_2D
glBindTexture GL_TEXTURE_2D [$::g_textureID(2) get 0]
glTexEnvi GL_TEXTURE_ENV GL_TEXTURE_ENV_MODE $::GL_COMBINE_ARB
glTexEnvi GL_TEXTURE_ENV GL_COMBINE_RGB_ARB $::GL_INTERPOLATE_ARB
glTexEnvi GL_TEXTURE_ENV GL_SOURCE0_RGB_ARB $::GL_PREVIOUS_ARB
glTexEnvi GL_TEXTURE_ENV GL_OPERAND0_RGB_ARB $::GL_SRC_COLOR
glTexEnvi GL_TEXTURE_ENV GL_SOURCE1_RGB_ARB $::GL_TEXTURE
glTexEnvi GL_TEXTURE_ENV GL_OPERAND1_RGB_ARB $::GL_SRC_COLOR
glTexEnvi GL_TEXTURE_ENV GL_SOURCE2_RGB_ARB $::GL_PRIMARY_COLOR_ARB
glTexEnvi GL_TEXTURE_ENV GL_OPERAND2_RGB_ARB $::GL_SRC_ALPHA
# Based on the contributions of texture 0 and texture 2 what is the
# contribution of texture 1? We don't really need this for the encoding
# process below. I'm simply calculating it so I can output its value later.
set g_fContributionOfTex1 [expr {1.0 - \
($g_fContributionOfTex0 + $g_fContributionOfTex2) }]
# Do some bounds checking...
if { $g_fContributionOfTex1 < 0.0 } {
set g_fContributionOfTex1 0.0
} elseif { $g_fContributionOfTex1 > 1.0 } {
set g_fContributionOfTex1 1.0
}
# Now, lets encode the blending information into the vertex color.
# The value set into the RGB part of the color controls the blending
# between texture 0 and texture 1, and the alpha part of the color
# controls the blending between textures1 and textures 2.
#
# Note: We use the contribution of texture 0 and 2 to infer or deduce
# the contribution of texture 1. We can do this because we know that the
# total contribution of our three textures must add up to 1.0f.
set rgbValue [expr { $g_fContributionOfTex0 / (1.0 - $g_fContributionOfTex2) }]
set alphaValue [expr { 1.0 - $g_fContributionOfTex2 }]
# Do some bounds checking...
if { $rgbValue < 0.0 } {
set rgbValue 0.0
} elseif { $rgbValue > 1.0 } {
set rgbValue 1.0
}
if { $alphaValue < 0.0 } {
set alphaValue 0.0
} elseif { $alphaValue > 1.0 } {
set alphaValue 1.0
}
glColor4f $rgbValue $rgbValue $rgbValue $alphaValue
# Render our quad with three sets of texture coordinates...
glBegin GL_QUADS
glMultiTexCoord2fARB GL_TEXTURE0_ARB 0.0 0.0
glMultiTexCoord2fARB GL_TEXTURE1_ARB 0.0 0.0
glMultiTexCoord2fARB GL_TEXTURE2_ARB 0.0 0.0
glVertex3f -1.0 -1.0 0.0
glMultiTexCoord2fARB GL_TEXTURE0_ARB 0.0 1.0
glMultiTexCoord2fARB GL_TEXTURE1_ARB 0.0 1.0
glMultiTexCoord2fARB GL_TEXTURE2_ARB 0.0 1.0
glVertex3f -1.0 1.0 0.0
glMultiTexCoord2fARB GL_TEXTURE0_ARB 1.0 1.0
glMultiTexCoord2fARB GL_TEXTURE1_ARB 1.0 1.0
glMultiTexCoord2fARB GL_TEXTURE2_ARB 1.0 1.0
glVertex3f 1.0 1.0 0.0
glMultiTexCoord2fARB GL_TEXTURE0_ARB 1.0 0.0
glMultiTexCoord2fARB GL_TEXTURE1_ARB 1.0 0.0
glMultiTexCoord2fARB GL_TEXTURE2_ARB 1.0 0.0
glVertex3f 1.0 -1.0 0.0
glEnd
# Output some info...
glActiveTextureARB GL_TEXTURE0_ARB
glDisable GL_TEXTURE_2D
glActiveTextureARB GL_TEXTURE1_ARB
glDisable GL_TEXTURE_2D
glActiveTextureARB GL_TEXTURE2_ARB
glDisable GL_TEXTURE_2D
set strTex0 [format "Contribution of Tex 0 = %.3f" $g_fContributionOfTex0]
set strTex1 [format "Contribution of Tex 1 = %.3f (Inferred by the values of Tex 0 & Tex 2)" $g_fContributionOfTex1]
set strTex2 [format "Contribution of Tex 2 = %.3f" $g_fContributionOfTex2]
set strRedValue [format "Red = %.3f" $rgbValue]
set strGreenValue [format "Green = %.3f" $rgbValue]
set strBlueValue [format "Blue = %.3f" $rgbValue]
set strAlphaValue [format "Alpha = %.3f" $alphaValue]
BeginRenderText $toglwin $::g_WinWidth $::g_WinHeight
glColor3f 1.0 1.0 0.0
RenderText 5 15 "Contribution of each texture for blending:"
glColor3f 1.0 1.0 1.0
RenderText 5 30 $strTex0
RenderText 5 45 $strTex1
RenderText 5 60 $strTex2
glColor3f 1.0 1.0 0.0
RenderText 5 355 "RGB values passed for interpolation of texture stage 1"
glColor3f 1.0 1.0 1.0
RenderText 5 370 $strRedValue
RenderText 5 385 $strGreenValue
RenderText 5 400 $strBlueValue
glColor3f 1.0 1.0 0.0
RenderText 5 415 "ALPHA value passed for interpolation of texture stage 2"
glColor3f 1.0 1.0 1.0
RenderText 5 430 $strAlphaValue
EndRenderText $toglwin
$toglwin swapbuffers
}
proc Cleanup {} {
if { [info exists ::g_textureID(0)] } {
glDeleteTextures 1 [$::g_textureID(0) get 0]
$::g_textureID(0) delete
}
if { [info exists ::g_textureID(1)] } {
glDeleteTextures 1 [$::g_textureID(1) get 0]
$::g_textureID(1) delete
}
if { [info exists ::g_textureID(2)] } {
glDeleteTextures 1 [$::g_textureID(2) get 0]
$::g_textureID(2) delete
}
foreach var [info globals g_*] {
uplevel #0 unset $var
}
}
proc ExitProg {} {
exit
}
frame .fr
pack .fr -expand 1 -fill both
togl .fr.toglwin -width $g_WinWidth -height $g_WinHeight \
-double true -depth true \
-createcommand CreateCallback \
-reshapecommand ReshapeCallback \
-displaycommand DisplayCallback
listbox .fr.usage -font $::g_listFont -height 5
label .fr.info
grid .fr.toglwin -row 0 -column 0 -sticky news
grid .fr.usage -row 1 -column 0 -sticky news
grid .fr.info -row 2 -column 0 -sticky news
grid rowconfigure .fr 0 -weight 1
grid columnconfigure .fr 0 -weight 1
set appTitle "Tcl3D demo: CodeSampler's Multi-Texture Blending"
wm title . $appTitle
# Watch For ESC Key And Quit Messages
wm protocol . WM_DELETE_WINDOW "ExitProg"
bind . <Key-Escape> "ExitProg"
bind . <Key-F1> "SetContributionOfTex0 0.01"
bind . <Key-F2> "SetContributionOfTex0 -0.01"
bind . <Key-F3> "SetContributionOfTex2 0.01"
bind . <Key-F4> "SetContributionOfTex2 -0.01"
bind . <Key-F5> "ToggleWireFrameMode"
bind . <Key-Up> "SetDistance 0.5"
bind . <Key-Down> "SetDistance -0.5"
bind .fr.toglwin <1> "SetMouseInput 1 %x %y"
bind .fr.toglwin <B1-Motion> "GetMouseInput 1 %x %y"
.fr.usage insert end "Key-Escape Exit"
.fr.usage insert end "Key-F1|F2 Increment|Decrement contribution of texture 0"
.fr.usage insert end "Key-F3|F4 Increment|Decrement contribution of texture 2"
.fr.usage insert end "Key-F5 Toggle wireframe mode"
.fr.usage insert end "Key-Up|Down Decrease|Increase distance"
.fr.usage configure -state disabled
PrintInfo [tcl3dOglGetInfoString]
|
