如何在绿龙渲染中获取剪辑距离和纹理

How to get clip distances and textures on green dragon rendering

来源为绿龙效果图。我的问题是如何让剪辑距离起作用?此外,纹理未显示为预期的输出。有什么可以在源代码中修改以按预期呈现程序的想法吗?

更新:在 Rabbid76 的出色帮助和精湛的答案下,剪辑距离和纹理加载正常!谢谢。

奖励示例:clipdistance_torus_package.zip 带有环面和纹理的剪辑距离示例!

预期输出:

文件到 运行:clipdistance_dragon.zip

#!/usr/bin/python3

import sys
import time
import ctypes

fullscreen = True

sys.path.append("./shared")

from sbmloader import SBMObject    # location of sbm file format loader
from ktxloader import KTXObject

from sbmath import m3dDegToRad, m3dRadToDeg, m3dTranslateMatrix44, m3dRotationMatrix44, m3dMultiply, m3dOrtho, m3dPerspective, rotation_matrix, translate, m3dScaleMatrix44, \
    scale, m3dLookAt, normalize

try:
    from OpenGL.GLUT import *
    from OpenGL.GL import *
    from OpenGL.GLU import *
    from OpenGL.raw.GL.ARB.vertex_array_object import glGenVertexArrays, glBindVertexArray
except:
    print ('''
    ERROR: PyOpenGL not installed properly.
        ''')
    sys.exit()

import numpy as np 
from math import cos, sin 
identityMatrix = [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]


myobject = SBMObject()

render_program = GLuint(0)
paused = False

class uniforms:
    proj_matrix = GLint(0)
    mv_matrix = GLint(0)
    clip_plane = GLint(0)
    clip_sphere = GLint(0)

uniform = uniforms()


def shader_load(filename, shader_type):
    result = GLuint(0)
    with open ( filename, "rb") as data:
        result = glCreateShader(shader_type)
        glShaderSource(result, data.read() )

    glCompileShader(result)
    if not glGetShaderiv(result, GL_COMPILE_STATUS):
        print( 'compile error:' )
        print( glGetShaderInfoLog(result) )
    return result

def link_from_shaders(shaders, shader_count, delete_shaders, check_errors=False):
    program = GLuint(0)
    program = glCreateProgram()

    for i in range(0, shader_count):
        glAttachShader(program, shaders[i])

    glLinkProgram(program)
    if not glGetProgramiv(program, GL_LINK_STATUS):
        print( 'link error:' )
        print( glGetProgramInfoLog(program) )

    if (delete_shaders):
        for i in range(0, shader_count):
            glDeleteShader(shaders[i])
    return program


def load_shaders():
    global render_program
    global uniform

    if (render_program):
        glDeleteProgram(render_program);

    shaders = [
        shader_load("render.vs.glsl", GL_VERTEX_SHADER),
        shader_load("render.fs.glsl", GL_FRAGMENT_SHADER)
    ]

    render_program = link_from_shaders(shaders, 2, True)

    uniform.proj_matrix = glGetUniformLocation(render_program, "proj_matrix");
    uniform.mv_matrix = glGetUniformLocation(render_program, "mv_matrix");
    uniform.clip_plane = glGetUniformLocation(render_program, "clip_plane");
    uniform.clip_sphere = glGetUniformLocation(render_program, "clip_sphere");

tex_dragon=None

class Scene:

    def __init__(self, width, height):
        global myobject, tex_dragon

        myobject.load("dragon.sbm");

        load_shaders()

        ktxobj = KTXObject()
        tex_dragon = ktxobj.ktx_load("pattern1.ktx")

    def display(self):
        global paused

        currentTime = time.time()

        black = [ 0.0, 0.0, 0.0, 0.0 ]
        one = 1.0

        last_time = 0.0
        total_time = 0.0

        if (not paused):
            total_time += (currentTime - last_time)
        last_time = currentTime

        f = total_time

        glClearBufferfv(GL_COLOR, 0, black)
        glClearBufferfv(GL_DEPTH, 0, one)

        glUseProgram(render_program)

        proj_matrix = (GLfloat * 16)(*identityMatrix)
        proj_matrix = m3dPerspective(m3dDegToRad(50.0), float(self.width) / float(self.height), 0.1, 1000.0)

        T1 = (GLfloat * 16)(*identityMatrix)
        m3dTranslateMatrix44(T1, 0.0, 0.0, -15.0)

        RY = (GLfloat * 16)(*identityMatrix)
        m3dRotationMatrix44(RY, f * 0.34, 0.0, 1.0, 0.0)

        T2 = (GLfloat * 16)(*identityMatrix)
        m3dTranslateMatrix44(T2, 0.0, -4.0, 0.0)

        mv_matrix = (GLfloat * 16)(*identityMatrix)
        mv_matrix = m3dMultiply(T1, m3dMultiply(RY, T2))


        RX = (GLfloat * 16)(*identityMatrix)
        m3dRotationMatrix44(RX, f * 6.0, 1.0, 0.0, 0.0)

        RY = (GLfloat * 16)(*identityMatrix)
        m3dRotationMatrix44(RY, f * 7.3, 0.0, 1.0, 0.0)

        plane_matrix = (GLfloat * 16)(*identityMatrix)
        plane_matrix = m3dMultiply(RX , RY )

        plane = plane_matrix[0:4]
        plane[3] = 0
        plane = normalize(plane)

        clip_sphere = [sin(f * 0.7) * 3.0, cos(f * 1.9) * 3.0, sin(f * 0.1) * 3.0, cos(f * 1.7) + 2.5]

        glUniformMatrix4fv(uniform.proj_matrix, 1, GL_FALSE, proj_matrix)
        glUniformMatrix4fv(uniform.mv_matrix, 1, GL_FALSE, mv_matrix)
        glUniform4fv(uniform.clip_plane, 1, plane)
        glUniform4fv(uniform.clip_sphere, 1, clip_sphere)

        glEnable(GL_DEPTH_TEST)
        glEnable(GL_CLIP_DISTANCE0)
        glEnable(GL_CLIP_DISTANCE1)

        glActiveTexture(GL_TEXTURE0)
        glBindTexture(GL_TEXTURE_2D, tex_dragon)
        myobject.render()

        glutSwapBuffers()

    def reshape(self, width, height):
        self.width = width
        self.height = height

    def keyboard(self, key, x, y ):
        global fullscreen
        global paused

        print ('key:' , key)
        if key == b'\x1b': # ESC
            sys.exit()

        elif key == b'f' or key == b'F': #fullscreen toggle

            if (fullscreen == True):
                glutReshapeWindow(512, 512)
                glutPositionWindow(int((1360/2)-(512/2)), int((768/2)-(512/2)))
                fullscreen = False
            else:
                glutFullScreen()
                fullscreen = True

        elif key == b'p' or key == b'P':
            paused = not paused

        elif key == b'r' or key == b'R':
            pass 

        print('done')

    def init(self):
        pass

    def timer(self, blah):

        glutPostRedisplay()
        glutTimerFunc( int(1/60), self.timer, 0)
        time.sleep(1/60.0)


if __name__ == '__main__':
    start = time.time()

    glutInit()


    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH)

    glutInitWindowSize(512, 512)

    w1 = glutCreateWindow('OpenGL SuperBible - Clip Distance')
    glutInitWindowPosition(int((1360/2)-(512/2)), int((768/2)-(512/2)))

    fullscreen = False
    #glutFullScreen()

    scene = Scene(512,512)
    glutReshapeFunc(scene.reshape)
    glutDisplayFunc(scene.display)
    glutKeyboardFunc(scene.keyboard)

    glutIdleFunc(scene.display)
    #glutTimerFunc( int(1/60), scene.timer, 0)

    scene.init()

    glutMainLoop()

clipdistance.cpp , source of sbmobject.cpp 移植到 python 以防万一 sbmloader.py 出现纹理问题。

示例中的 C++ 代码

vmath::mat4 plane_matrix = vmath::rotate(f * 6.0f, 1.0f, 0.0f, 0.0f) *
                           vmath::rotate(f * 7.3f, 0.0f, 1.0f, 0.0f);

对应以下python代码

RX = (GLfloat * 16)(*identityMatrix)
m3dRotationMatrix44(RX, f * 6.0, 1.0, 0.0, 0.0)

RY = (GLfloat * 16)(*identityMatrix)
m3dRotationMatrix44(RY, f * 7.3, 0.0, 1.0, 0.0)

plane_matrix = (GLfloat * 16)(*identityMatrix)
plane_matrix = m3dMultiply(RX , RY)

请注意,您必须在矩阵乘法中交换 RXRY


您的函数 lengthnormalize 只能处理具有 3 个分量的向量 (x, y, z)。比较示例中的 C++ 函数 vmath::normalize 也可以处理具有 4 个分量的向量 (x, y, z, w)
此外 "division by zero" 处理在 normalize.

中缺失

调整函数 normalizelength,以处理任何矢量大小。
如果向量的长度为 0,则其所有分量均为 0。对此没有正确的解决方案,因此只需 return 向量本身的副本。

def length(v):
    sum_sq = sum([s*s for s in v])
    return math.sqrt(sum_sq)
def normalize(v):
    l = length(v)
    if l == 0.0:
        return v[:]
    return [s/l for s in v]

现在您可以移植示例中的 C++ 代码

vmath::vec4 plane = plane_matrix[0];
plane[3] = 0.0f;
plane = vmath::normalize(plane);

非常直接 python:

plane = plane_matrix[0:4]
plane[3] = 0
plane = normalize(plane)

此外,sbmloader 模块中存在问题。
只指定了顶点坐标和纹理坐标的数组。跳过法向量。

跳过这行

if attrib.name=='position' or attrib.name=='map1':

解决问题:

for attrib_i, attrib in enumerate(vertex_attrib_chunk.attrib_data):

    #if attrib.name=='position' or attrib.name=='map1': 

    glVertexAttribPointer(attrib_i,
        attrib.size, attrib.type,
        GL_TRUE if (attrib.flags & SB6M_VERTEX_ATTRIB_FLAG_NORMALIZED) != 0 else GL_FALSE,
        attrib.stride, ctypes.c_void_p(int(attrib.data_offset)))
    glEnableVertexAttribArray(attrib_i)


如果您还想将纹理包裹到模型中,则必须将纹理坐标属性添加到顶点着色器:

layout (location = 2) in vec2 tc;

并通过输出将其传递到下一个着色器阶段

out VS_OUT
{
    vec3 N;
    vec3 L;
    vec3 V;
    vec2 T;
} vs_out;
void main()
{
   // ...

   vs_out.T = tc;

   // ...  
}    

在片段着色器中,您必须添加纹理采样器统一

layout (binding = 0) uniform sampler2D tex;

从纹理中读取颜色

vec4 texColor = texture(tex, fs_in.T); 

将输出颜色乘以纹理颜色

color = vec4(diffuse + specular + rim, 1.0) * texColor;

片段着色器(注意,我改了diffuse_albedo):

#version 420 core

// Output
layout (location = 0) out vec4 color;

// Input from vertex shader
in VS_OUT
{
    vec3 N;
    vec3 L;
    vec3 V;
    vec2 T;
} fs_in;

// Material properties
uniform vec3 diffuse_albedo = vec3(0.5);
uniform vec3 specular_albedo = vec3(0.7);
uniform float specular_power = 128.0;
uniform vec3 rim_color = vec3(0.1, 0.2, 0.2);
uniform float rim_power = 5.0;
layout (binding = 0) uniform sampler2D tex;

vec3 calculate_rim(vec3 N, vec3 V)
{
    float f = 1.0 - dot(N, V);

    f = smoothstep(0.0, 1.0, f);
    f = pow(f, rim_power);

    return f * rim_color;
}

void main(void)
{
    // Normalize the incoming N, L and V vectors
    vec3 N = normalize(fs_in.N);
    vec3 L = normalize(fs_in.L);
    vec3 V = normalize(fs_in.V);

    // Calculate R locally
    vec3 R = reflect(-L, N);

    // Compute the diffuse and specular components for each fragment
    vec3 diffuse = max(dot(N, L), 0.0) * diffuse_albedo;
    vec3 specular = pow(max(dot(R, V), 0.0), specular_power) * specular_albedo;
    vec3 rim = calculate_rim(N, V);

    // read color from the texture
    vec4 texColor = texture(tex, fs_in.T);

    // Write final color to the framebuffer
    color = vec4(diffuse + specular + rim, 1.0) * texColor;
}

我建议添加着色器编译和link错误记录:

glCompileShader(result)
if not glGetShaderiv(result, GL_COMPILE_STATUS):
    print( 'compile error:' )
    print( glGetShaderInfoLog(result) )
glLinkProgram(program)
if not glGetProgramiv(program, GL_LINK_STATUS):
    print( 'link error:' )
    print( glGetProgramInfoLog(program) )

在应用程序初始化时读取纹理

class Scene:

    def __init__(self, width, height):
        global myobject, tex_dragon

        myobject.load("dragon.sbm")

        load_shaders()

        ktxobj = KTXObject()
        tex_dragon = ktxobj.ktx_load("texture_file_name.ktx")

在绘制模型之前绑定纹理

glActiveTexture(GL_TEXTURE0)
glBindTexture(GL_TEXTURE_2D, tex_dragon)
myobject.render()