使用 OGL 4.0 GLSL 中的曲面细分着色器更改几何体
一个简单的 OGL 4.0 GLSL 着色器程序,显示如何使用曲面细分着色器向几何体添加细节。该程序使用 python 脚本执行。要运行脚本,必须安装 PyOpenGL 和 NumPy。
此示例中的基本网格是由 20 个三角形组成的二十面体。曲面细分控制着色器定义每个三角形如何分成一组许多小部件。在对三角形进行细分时,生成的数据是基于原始三角形的重心坐标。曲面细分评估着色器从以这种方式获得的数据生成新几何。在这个例子中,每个三角形在中间得到一个峰值,它从 icosader 的中心向外升起。以这种方式,产生比原始二十面体更复杂的几何形状。
顶点着色器
tess.vert
layout (location = 0) in vec3 inPos;
layout (location = 1) in vec3 inNV;
out TVertexData
{
vec3 pos;
vec3 nv;
} outData;
uniform mat4 u_projectionMat44;
uniform mat4 u_modelViewMat44;
uniform mat3 u_normalMat33;
void main()
{
vec4 viewPos = u_modelViewMat44 * vec4( inPos, 1.0 );
outData.pos = viewPos.xyz / viewPos.w;
outData.nv = u_normalMat33 * normalize( inNV );
gl_Position = u_projectionMat44 * viewPos;
}
曲面细分控制着色器
tess.tctrl
#version 400
layout( vertices=3 ) out;
in TVertexData
{
vec3 pos;
vec3 nv;
} inData[];
out TVertexData
{
vec3 pos;
vec3 nv;
} outData[];
void main()
{
outData[gl_InvocationID].pos = inData[gl_InvocationID].pos;
outData[gl_InvocationID].nv = inData[gl_InvocationID].nv;
if ( gl_InvocationID == 0 )
{
gl_TessLevelOuter[0] = 10.0;
gl_TessLevelOuter[1] = 10.0;
gl_TessLevelOuter[2] = 10.0;
gl_TessLevelInner[0] = 10.0;
}
}
曲面细分评估着色器
tess.teval
#version 400
layout(triangles, equal_spacing, ccw) in;
in TVertexData
{
vec3 pos;
vec3 nv;
} inData[];
out TTessData
{
vec3 pos;
vec3 nv;
float height;
} outData;
uniform mat4 u_projectionMat44;
void main()
{
float sideLen[3] = float[3]
(
length( inData[1].pos - inData[0].pos ),
length( inData[2].pos - inData[1].pos ),
length( inData[0].pos - inData[2].pos )
);
float s = ( sideLen[0] + sideLen[1] + sideLen[2] ) / 2.0;
float rad = sqrt( (s - sideLen[0]) * (s - sideLen[1]) * (s - sideLen[2]) / s );
vec3 cpt = ( inData[0].pos + inData[1].pos + inData[2].pos ) / 3.0;
vec3 pos = inData[0].pos * gl_TessCoord.x + inData[1].pos * gl_TessCoord.y + inData[2].pos * gl_TessCoord.z;
vec3 nv = normalize( inData[0].nv * gl_TessCoord.x + inData[1].nv * gl_TessCoord.y + inData[2].nv * gl_TessCoord.z );
float cptDist = length( cpt - pos );
float sizeRelation = 1.0 - min( rad, cptDist ) / rad;
float height = pow( sizeRelation, 2.0 );
outData.pos = pos + nv * height * rad;
outData.nv = mix( nv, normalize( pos - cpt ), height );
outData.height = height;
gl_Position = u_projectionMat44 * vec4( outData.pos, 1.0 );
}
片段着色器
tess.frag
#version 400
in TTessData
{
vec3 pos;
vec3 nv;
float height;
} inData;
out vec4 fragColor;
uniform sampler2D u_texture;
uniform UB_material
{
float u_roughness;
float u_fresnel0;
vec4 u_color;
vec4 u_specularTint;
};
struct TLightSource
{
vec4 ambient;
vec4 diffuse;
vec4 specular;
vec4 dir;
};
uniform UB_lightSource
{
TLightSource u_lightSource;
};
float Fresnel_Schlick( in float theta );
vec3 LightModel( in vec3 esPt, in vec3 esPtNV, in vec3 col, in vec4 specularTint, in float roughness, in float fresnel0 );
void main()
{
vec3 col = mix( u_color.rgb, vec3( 1.0, 1.0, 1.0 ), inData.height );
vec3 lightCol = LightModel( inData.pos, inData.nv, col, u_specularTint, u_roughness, u_fresnel0 );
fragColor = vec4( clamp( lightCol, 0.0, 1.0 ), 1.0 );
}
float Fresnel_Schlick( in float theta )
{
float m = clamp( 1.0 - theta, 0.0, 1.0 );
float m2 = m * m;
return m2 * m2 * m; // pow( m, 5.0 )
}
vec3 LightModel( in vec3 esPt, in vec3 esPtNV, in vec3 col, in vec4 specularTint, in float roughness, in float fresnel0 )
{
vec3 esVLight = normalize( -u_lightSource.dir.xyz );
vec3 esVEye = normalize( -esPt );
vec3 halfVector = normalize( esVEye + esVLight );
float HdotL = dot( halfVector, esVLight );
float NdotL = dot( esPtNV, esVLight );
float NdotV = dot( esPtNV, esVEye );
float NdotH = dot( esPtNV, halfVector );
float NdotH2 = NdotH * NdotH;
float NdotL_clamped = max( NdotL, 0.0 );
float NdotV_clamped = max( NdotV, 0.0 );
float m2 = roughness * roughness;
// Lambertian diffuse
float k_diffuse = NdotL_clamped;
// Schlick approximation
float fresnel = fresnel0 + ( 1.0 - fresnel0 ) * Fresnel_Schlick( HdotL );
// Beckmann distribution
float distribution = max( 0.0, exp( ( NdotH2 - 1.0 ) / ( m2 * NdotH2 ) ) / ( 3.14159265 * m2 * NdotH2 * NdotH2 ) );
// Torrance-Sparrow geometric term
float geometric_att = min( 1.0, min( 2.0 * NdotH * NdotV_clamped / HdotL, 2.0 * NdotH * NdotL_clamped / HdotL ) );
// Microfacet bidirectional reflectance distribution function
float k_specular = fresnel * distribution * geometric_att / ( 4.0 * NdotL_clamped * NdotV_clamped );
vec3 lightColor = col.rgb * u_lightSource.ambient.rgb +
max( 0.0, k_diffuse ) * col.rgb * u_lightSource.diffuse.rgb +
max( 0.0, k_specular ) * mix( col.rgb, specularTint.rgb, specularTint.a ) * u_lightSource.specular.rgb;
return lightColor;
}
Python 脚本
from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
import numpy as np
from time import time
import math
import sys
sin120 = 0.8660254
rotateCamera = False
# draw event
def OnDraw():
dist = 3.0
currentTime = time()
comeraRotAng = CalcAng( currentTime, 10.0 )
# set up projection matrix
prjMat = Perspective(90.0, wndW/wndH, 0.5, 100.0)
# set up view matrix
viewMat = np.matrix(np.identity(4), copy=False, dtype='float32')
viewMat = Translate( viewMat, np.array( [0.0, 0.0, -12.0] ) )
viewMat = RotateView( viewMat, [30.0, comeraRotAng if rotateCamera else 0.0, 0.0] )
# set up light source
lightSourceBuffer.BindDataFloat(b'u_lightSource.dir', TransformVec4([-1.0, -1.0, -5.0, 0.0], viewMat) )
# set up icosahedron model matrix
icoModelMat = np.matrix(np.identity(4), copy=False, dtype='float32')
if not rotateCamera: icoModelMat = RotateY( icoModelMat, comeraRotAng )
icoModelMat = Scale( icoModelMat, np.repeat( 5, 3 ) )
icoModelMat = RotateY( icoModelMat, CalcAng( currentTime, 17.0 ) )
icoModelMat = RotateX( icoModelMat, CalcAng( currentTime, 13.0 ) )
# set up attributes and shader program
glEnable( GL_DEPTH_TEST )
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT )
glUseProgram( shaderProgram )
glUniformMatrix4fv( projectionMatLocation, 1, GL_FALSE, prjMat )
lightSourceBuffer.BindToTarget()
# draw icosahedron
icoMaterialBuffer.BindToTarget()
modelViewMat = Multiply(viewMat, icoModelMat)
glUniformMatrix4fv( modelViewMatLocation, 1, GL_FALSE, modelViewMat )
glUniformMatrix3fv( normalMatLocation, 1, GL_FALSE, ToMat33(modelViewMat) )
glBindVertexArray( icoVAObj )
glPatchParameteri( GL_PATCH_VERTICES, 3 )
glDrawArrays( GL_PATCHES, 0, len(icoPosData) )
glutSwapBuffers()
def Fract(val): return val - math.trunc(val)
def CalcAng(currentTime, intervall): return Fract( (currentTime - startTime) / intervall ) * 360.0
def CalcMove(currentTime, intervall, range):
pos = Fract( (currentTime - startTime) / intervall ) * 2.0
pos = pos if pos < 1.0 else (2.0-pos)
return range[0] + (range[1] - range[0]) * pos
# read shader program and compile shader
def CompileShader( sourceFileName, shaderStage ):
with open( sourceFileName, 'r' ) as sourceFile:
sourceCode = sourceFile.read()
nameMap = { GL_VERTEX_SHADER: 'vertex', GL_GEOMETRY_SHADER: 'geometry', GL_FRAGMENT_SHADER: 'fragment' }
print( '\n%s shader code:' % nameMap.get(shaderStage, '') )
print( sourceCode )
shaderObj = glCreateShader( shaderStage )
glShaderSource( shaderObj, sourceCode )
glCompileShader( shaderObj )
result = glGetShaderiv( shaderObj, GL_COMPILE_STATUS )
if not (result):
print( glGetShaderInfoLog( shaderObj ) )
sys.exit()
return shaderObj
# link shader objects to shader program
def LinkProgram( shaderObjs ):
shaderProgram = glCreateProgram()
for shObj in shaderObjs:
glAttachShader( shaderProgram, shObj )
glLinkProgram( shaderProgram )
result = glGetProgramiv( shaderProgram, GL_LINK_STATUS )
if not (result):
print( 'link error:' )
print( glGetProgramInfoLog( shaderProgram ) )
sys.exit()
return shaderProgram
# create vertex array object
def CreateVAO( dataArrays ):
noOfBuffers = len(dataArrays)
buffers = glGenBuffers(noOfBuffers)
newVAObj = glGenVertexArrays( 1 )
glBindVertexArray( newVAObj )
for inx in range(0, noOfBuffers):
vertexSize, dataArr = dataArrays[inx]
arr = np.array( dataArr, dtype='float32' )
glBindBuffer( GL_ARRAY_BUFFER, buffers[inx] )
glBufferData( GL_ARRAY_BUFFER, arr, GL_STATIC_DRAW )
glEnableVertexAttribArray( inx )
glVertexAttribPointer( inx, vertexSize, GL_FLOAT, GL_FALSE, 0, None )
return newVAObj
# representation of a uniform block
class UniformBlock:
def __init__(self, shaderProg, name):
self.shaderProg = shaderProg
self.name = name
def Link(self, bindingPoint):
self.bindingPoint = bindingPoint
self.noOfUniforms = glGetProgramiv(self.shaderProg, GL_ACTIVE_UNIFORMS)
self.maxUniformNameLen = glGetProgramiv(self.shaderProg, GL_ACTIVE_UNIFORM_MAX_LENGTH)
self.index = glGetUniformBlockIndex(self.shaderProg, self.name)
intData = np.zeros(1, dtype=int)
glGetActiveUniformBlockiv(self.shaderProg, self.index, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, intData)
self.count = intData[0]
self.indices = np.zeros(self.count, dtype=int)
glGetActiveUniformBlockiv(self.shaderProg, self.index, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, self.indices)
self.offsets = np.zeros(self.count, dtype=int)
glGetActiveUniformsiv(self.shaderProg, self.count, self.indices, GL_UNIFORM_OFFSET, self.offsets)
strLengthData = np.zeros(1, dtype=int)
arraysizeData = np.zeros(1, dtype=int)
typeData = np.zeros(1, dtype='uint32')
nameData = np.chararray(self.maxUniformNameLen+1)
self.namemap = {}
self.dataSize = 0
for inx in range(0, len(self.indices)):
glGetActiveUniform( self.shaderProg, self.indices[inx], self.maxUniformNameLen, strLengthData, arraysizeData, typeData, nameData.data )
name = nameData.tostring()[:strLengthData[0]]
self.namemap[name] = inx
self.dataSize = max(self.dataSize, self.offsets[inx] + arraysizeData * 16)
glUniformBlockBinding(self.shaderProg, self.index, self.bindingPoint)
print('\nuniform block %s size:%4d' % (self.name, self.dataSize))
for uName in self.namemap:
print( ' %-40s index:%2d offset:%4d' % (uName, self.indices[self.namemap[uName]], self.offsets[self.namemap [uName]]) )
# representation of a uniform block buffer
class UniformBlockBuffer:
def __init__(self, ub):
self.namemap = ub.namemap
self.offsets = ub.offsets
self.bindingPoint = ub.bindingPoint
self.object = glGenBuffers(1)
self.dataSize = ub.dataSize
glBindBuffer(GL_UNIFORM_BUFFER, self.object)
dataArray = np.zeros(self.dataSize//4, dtype='float32')
glBufferData(GL_UNIFORM_BUFFER, self.dataSize, dataArray, GL_DYNAMIC_DRAW)
def BindToTarget(self):
glBindBuffer(GL_UNIFORM_BUFFER, self.object)
glBindBufferBase(GL_UNIFORM_BUFFER, self.bindingPoint, self.object)
def BindDataFloat(self, name, dataArr):
glBindBuffer(GL_UNIFORM_BUFFER, self.object)
dataArray = np.array(dataArr, dtype='float32')
glBufferSubData(GL_UNIFORM_BUFFER, self.offsets[self.namemap[name]], len(dataArr)*4, dataArray)
def Translate(matA, trans):
matB = np.copy(matA)
for i in range(0, 4): matB[3,i] = matA[0,i] * trans[0] + matA[1,i] * trans[1] + matA[2,i] * trans[2] + matA[3,i]
return matB
def Scale(matA, s):
matB = np.copy(matA)
for i0 in range(0, 3):
for i1 in range(0, 4): matB[i0,i1] = matA[i0,i1] * s[i0]
return matB
def RotateHlp(matA, angDeg, a0, a1):
matB = np.copy(matA)
ang = math.radians(angDeg)
sinAng, cosAng = math.sin(ang), math.cos(ang)
for i in range(0, 4):
matB[a0,i] = matA[a0,i] * cosAng + matA[a1,i] * sinAng
matB[a1,i] = matA[a0,i] * -sinAng + matA[a1,i] * cosAng
return matB
def RotateX(matA, angDeg): return RotateHlp(matA, angDeg, 1, 2)
def RotateY(matA, angDeg): return RotateHlp(matA, angDeg, 2, 0)
def RotateZ(matA, angDeg): return RotateHlp(matA, angDeg, 0, 1)
def RotateView(matA, angDeg): return RotateZ(RotateY(RotateX(matA, angDeg[0]), angDeg[1]), angDeg[2])
def Multiply(matA, matB):
matC = np.copy(matA)
for i0 in range(0, 4):
for i1 in range(0, 4):
matC[i0,i1] = matB[i0,0] * matA[0,i1] + matB[i0,1] * matA[1,i1] + matB[i0,2] * matA[2,i1] + matB[i0,3] * matA[3,i1]
return matC
def ToMat33(mat44):
mat33 = np.matrix(np.identity(3), copy=False, dtype='float32')
for i0 in range(0, 3):
for i1 in range(0, 3): mat33[i0, i1] = mat44[i0, i1]
return mat33
def TransformVec4(vecA,mat44):
vecB = np.zeros(4, dtype='float32')
for i0 in range(0, 4):
vecB[i0] = vecA[0] * mat44[0,i0] + vecA[1] * mat44[1,i0] + vecA[2] * mat44[2,i0] + vecA[3] * mat44[3,i0]
return vecB
def Perspective(fov, aspectRatio, near, far):
fn, f_n = far + near, far - near
r, t = aspectRatio, 1.0 / math.tan( math.radians(fov) / 2.0 )
return np.matrix( [ [t/r,0,0,0], [0,t,0,0], [0,0,-fn/f_n,-2.0*far*near/f_n], [0,0,-1,0] ] )
def AddToBuffer( buffer, data, count=1 ):
for inx_c in range(0, count):
for inx_s in range(0, len(data)): buffer.append( data[inx_s] )
# initialize glut
glutInit()
# create window
wndW, wndH = 800, 600
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH)
glutInitWindowPosition(0, 0)
glutInitWindowSize(wndW, wndH)
wndID = glutCreateWindow(b'OGL window')
glutDisplayFunc(OnDraw)
glutIdleFunc(OnDraw)
# define icosahedron vertex array opject
icoPts = [
( 0.000, 0.000, 1.000), ( 0.894, 0.000, 0.447), ( 0.276, 0.851, 0.447), (-0.724, 0.526, 0.447),
(-0.724, -0.526, 0.447), ( 0.276, -0.851, 0.447), ( 0.724, 0.526, -0.447), (-0.276, 0.851, -0.447),
(-0.894, 0.000, -0.447), (-0.276, -0.851, -0.447), ( 0.724, -0.526, -0.447), ( 0.000, 0.000, -1.000) ]
icoCol = [ [1.0, 0.0, 0.0], [0.0, 0.0, 1.0], [1.0, 1.0, 0.0], [0.0, 1.0, 0.0], [1.0, 0.5, 0.0], [1.0, 0.0, 1.0] ]
icoIndices = [
2, 0, 1, 3, 0, 2, 4, 0, 3, 5, 0, 4, 1, 0, 5, 11, 7, 6, 11, 8, 7, 11, 9, 8, 11, 10, 9, 11, 6, 10,
1, 6, 2, 2, 7, 3, 3, 8, 4, 4, 9, 5, 5, 10, 1, 2, 6, 7, 3, 7, 8, 4, 8, 9, 5, 9, 10, 1, 10, 6 ]
icoPosData = []
for inx in icoIndices: AddToBuffer( icoPosData, icoPts[inx] )
icoNVData = []
for inx_nv in range(0, len(icoIndices) // 3):
nv = [0.0, 0.0, 0.0]
for inx_p in range(0, 3):
for inx_s in range(0, 3): nv[inx_s] += icoPts[ icoIndices[inx_nv*3 + inx_p] ][inx_s]
AddToBuffer( icoNVData, nv, 3 )
icoVAObj = CreateVAO( [ (3, icoPosData), (3, icoNVData) ] )
# load, compile and link shader
shaderProgram = LinkProgram( [
CompileShader( 'tess.vert', GL_VERTEX_SHADER ),
CompileShader( 'tess.tctrl', GL_TESS_CONTROL_SHADER ),
CompileShader( 'tess.teval', GL_TESS_EVALUATION_SHADER ),
CompileShader( 'tess.frag', GL_FRAGMENT_SHADER )
] )
# get unifor locations
projectionMatLocation = glGetUniformLocation(shaderProgram, "u_projectionMat44")
modelViewMatLocation = glGetUniformLocation(shaderProgram, "u_modelViewMat44")
normalMatLocation = glGetUniformLocation(shaderProgram, "u_normalMat33")
# linke uniform blocks
ubMaterial = UniformBlock(shaderProgram, "UB_material")
ubLightSource = UniformBlock(shaderProgram, "UB_lightSource")
ubMaterial.Link(1)
ubLightSource.Link(2)
# create uniform block buffers
lightSourceBuffer = UniformBlockBuffer(ubLightSource)
lightSourceBuffer.BindDataFloat(b'u_lightSource.ambient', [0.2, 0.2, 0.2, 1.0])
lightSourceBuffer.BindDataFloat(b'u_lightSource.diffuse', [0.2, 0.2, 0.2, 1.0])
lightSourceBuffer.BindDataFloat(b'u_lightSource.specular', [1.0, 1.0, 1.0, 1.0])
icoMaterialBuffer = UniformBlockBuffer(ubMaterial)
icoMaterialBuffer.BindDataFloat(b'u_roughness', [0.45])
icoMaterialBuffer.BindDataFloat(b'u_fresnel0', [0.4])
icoMaterialBuffer.BindDataFloat(b'u_color', [0.6, 0.5, 0.8, 1.0])
icoMaterialBuffer.BindDataFloat(b'u_specularTint',[1.0, 0.5, 0.5, 0.8])
# start main loop
startTime = time()
glutMainLoop()