使用cl-opengl编写OBJ加载器时glDrawElements无法渲染问题
我仔细排查了你的代码,发现几个核心问题导致渲染空白,下面逐个拆解并给出修复方案:
1. 全局变量引用错误
你定义了全局变量*vertices*和*elements*,但在load-obj函数里直接使用了无星号的vertices和elements——在Common Lisp中,这会被视为未绑定的局部变量,导致顶点和元素数据根本没被正确加载到全局数组里。
修复:把load-obj里的vertices替换为*vertices*,elements替换为*elements*。
2. 顶点属性指针参数错误
你的顶点是3维向量(vec3),但gl:vertex-attrib-pointer的第二个参数设成了4,这会让OpenGL每次读取4个float作为一个顶点,直接导致顶点数据错位,最终物体被渲染到视锥体之外,屏幕自然显示空白。
修复:将(gl:vertex-attrib-pointer 0 4 :float nil 0 0)修改为(gl:vertex-attrib-pointer 0 3 :float nil 0 0)。
3. glDrawElements调用错误
你调用(gl:draw-elements :triangles glarray-2)的方式完全错误:glDrawElements的第二个参数应该是元素的数量(整数),而非GL数组对象,同时必须指定元素的类型。
修复:改成(gl:draw-elements :triangles (length *elements*) :unsigned-short 0)。
4. SDL OpenGL上下文配置错误
你设置(sdl2:gl-set-attr :context-profile-mask 0)不符合OpenGL 3.3核心上下文的要求,正确的掩码应该是核心模式,否则可能无法创建符合规范的上下文,导致着色器或渲染功能异常。
修复:将该行修改为(sdl2:gl-set-attr :context-profile-mask sdl2-ffi::+SDL-GL-CONTEXT-PROFILE-CORE+)。
5. 其他细节优化
load-obj里的文件打开可以用更简洁安全的with-open-file,避免手动处理流的问题;- 别忘了释放
glarray-2这个GL数组,避免内存泄漏; - 添加程序链接日志打印,方便排查着色器链接阶段的问题。
修正后的完整代码
(require :cl-opengl) (require :sdl2) (defvar *vertices* (make-array 9 :fill-pointer 0)) (defvar *elements* (make-array 9 :fill-pointer 0)) (defvar *vertex-shader* " #version 330 core layout (location = 0) in vec3 aPos; void main() { gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); } ") (defvar *fragment-shader* " #version 330 core out vec4 FragColor; void main() { FragColor = vec4(0.95f, 0.98f, 0.65f, 1.0f); } ") (defun split-str-1 (string &optional (separator " ") (r nil)) (let ((n (position separator string :from-end t :test #'(lambda (x y) (find y x :test #'string=))))) (if n (split-str-1 (subseq string 0 n) separator (cons (subseq string (1+ n)) r)) (cons string r)))) (defun split-str (string &optional (separator " ")) (split-str-1 string separator)) (defun parse-float (number) (with-input-from-string (in number) (read in))) (defun load-obj (file-name) (with-open-file (source file-name) (loop for line = (read-line source nil nil) while line do (let* ((split-line (split-str line " ")) (header (car split-line)) (rest (cdr split-line))) (cond ((string= header "v") (dolist (vertex rest) (vector-push-extend (parse-float vertex) *vertices*))) ((string= header "f") (dolist (face rest) (let ((element (parse-integer (car (split-str face "/"))))) (vector-push-extend (- element 1) *elements*))))))))) (defun main () (load-obj "tortoise.obj") (sdl2:with-init (:everything) (sdl2:gl-set-attr :context-profile-mask sdl2-ffi::+SDL-GL-CONTEXT-PROFILE-CORE+) (sdl2:gl-set-attr :context-major-version 3) (sdl2:gl-set-attr :context-minor-version 3) (sdl2:with-window (win :width 800 :height 600 :flags `(:shown :opengl)) (sdl2:with-gl-context (gl-context win) (sdl2:gl-make-current win gl-context) (gl:viewport 0 0 800 600) (gl:clear-color 0.957 0.376 0.286 1.0) (let ((glarray (gl:alloc-gl-array :float (length *vertices*))) (glarray-2 (gl:alloc-gl-array :unsigned-short (length *elements*)))) (dotimes (i (length *elements*)) (setf (gl:glaref glarray-2 i) (aref *elements* i))) (dotimes (i (length *vertices*)) (setf (gl:glaref glarray i) (aref *vertices* i))) (let ((vbo (gl:gen-buffer)) (vao (gl:gen-vertex-array)) (ebo (gl:gen-buffer))) (gl:bind-vertex-array vao) (gl:bind-buffer :array-buffer vbo) (gl:buffer-data :array-buffer :static-draw glarray) (gl:free-gl-array glarray) (gl:bind-buffer :element-array-buffer ebo) (gl:buffer-data :element-array-buffer :static-draw glarray-2) (gl:free-gl-array glarray-2) (gl:vertex-attrib-pointer 0 3 :float nil 0 0) (gl:enable-vertex-attrib-array 0) (let ((vertex-shader (gl:create-shader :vertex-shader)) (fragment-shader (gl:create-shader :fragment-shader))) (gl:shader-source vertex-shader *vertex-shader*) (gl:shader-source fragment-shader *fragment-shader*) (gl:compile-shader vertex-shader) (gl:compile-shader fragment-shader) (print (gl:get-shader-info-log vertex-shader)) (print (gl:get-shader-info-log fragment-shader)) (let ((program (gl:create-program))) (gl:attach-shader program vertex-shader) (gl:attach-shader program fragment-shader) (gl:link-program program) (print (gl:get-program-info-log program)) (gl:delete-shader vertex-shader) (gl:delete-shader fragment-shader) (gl:use-program program))) (sdl2:with-event-loop (:method :poll) (:idle () (gl:clear :color-buffer) (gl:bind-vertex-array vao) (gl:draw-elements :triangles (length *elements*) :unsigned-short 0) (gl:flush) (sdl2:gl-swap-window win)) (:quit () t))))))))
如果修复后仍然空白,建议检查你的OBJ模型坐标是否在OpenGL标准化设备坐标(NDC)范围内(x/y/z值在[-1,1]之间)。如果模型尺寸过大,需要添加投影和视图矩阵来将模型缩放到可见区域。
内容的提问来源于stack exchange,提问作者PavelF

