iOS OpenGL渲染YUV数据

2023-10-27

链接:http://www.jianshu.com/p/39cde80d60e2

本文主要介绍使用OpenGL ES来渲染I420(YUV420P) , NV12(YUV420SP)的方法,关于YUV的知识,可以看这里《YUV颜色编码解析》,同样会用到一些简单的OpenGL shader知识,可以看看OpenGL的着色器语言。为了书写方便,以下所谈的OpenGL特指OpenGL ES。

OpenGL ES是OpenGL的精简版本,主要针对于手机、游戏主机等嵌入式设备,它提供了一套设备图形硬件的软件接口,通过直接操作图形硬件,使我们能够高效地绘制图形。OpenGL在iOS架构中属于媒体层,与quartz(core graphics)类似,是相对底层的技术,可以控制每一帧的图形绘制。由于图形渲染是通过图形硬件(GPU)来完成的,相对于使用CPU,能够获得更高的帧率同时不会因为负载过大而造成卡顿。


OpenGL处于绘制接口的底层

创建GLView

我们需要创建一个用来展示OpenGL绘制内容的View,只需要将UIView的根图层(underlying layer)替换成CAEAGLLayer实例即可。通过覆盖UIView的类方法+(Class)layerClass,可以实现这一点,CAEAGLLayer默认是透明的,这会影响性能,所以将它设为不透明。

+ (class)layerClass {
    return [CAEAGLLayer class];
}
- (void)setupLayer {
    _eaglLayer = (CAEAGLLayer*) self.layer;
    _eaglLayer.opaque = YES;
}

创建EAGLContext

EAGLContext对象管理OpenGL绘制所需要的所有信息,和Quartz 2D所使用的CGContext类似。

- (void)setupContext {   
//创建一个OpenGLES 2.0接口的context
    EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
    _context = [[EAGLContext alloc] initWithAPI:api];
    if (!_context) {
        NSLog(@"Failed to initialize OpenGLES 2.0 context");
        exit(1);
    }
 //将其设置为current context
    if (![EAGLContext setCurrentContext:_context]) {
        NSLog(@"Failed to set current OpenGL context");
        exit(1);
    }
}

创建render buffer

render buffer用来存储将要绘制到屏幕上图像。OpenGL中的对象都需要创建、绑定,并且都是ID引用的。

- (void)setupRenderBuffer {
    //创建一个render buffer对象,并绑定到GL_RENDERBUFFER目标上
    glGenRenderbuffers(1, &_renderBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer); 
    //为render buffer分配存储空间
    [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer];    
}

创建frame buffer

一个frame buffer对象包括render buffer, depth buffer, stencil buffer等,拥有OpenGL绘制时需要的信息。

- (void)setupFrameBuffer {    
    GLuint framebuffer;
    glGenFramebuffers(1, &framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    //将之前创建的render buffer附着到frame buffer作为其logical buffer
    //GL_COLOR_ATTACHMENT0指定第一个颜色缓冲区附着点
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 
        GL_RENDERBUFFER, _renderBuffer);
 }

glFramebufferRenderbuffer调用后,render buffer通过GL_COLOR_ATTACHMENT0引用使用render buffer

渲染

- (void)render {
//设置用来清除屏幕的颜色,类似于quartz中设置CGcontext画笔的颜色
    glClearColor(0, 0, 0, 1.0);
    //执行清除操作,设置render buffer中的像素颜色为上一步指定的颜色
    glClear(GL_COLOR_BUFFER_BIT);
    //渲染render buffer中的图像到GLView的CAEAGLLayer
    [_context presentRenderbuffer:GL_RENDERBUFFER];
}

shader(着色器)

shader是上是在GPU上执行的程序,保存在.glsl文件中或以字符串形式写在OpenGL代码里,使用GLSL(OpenGL shading language)语言编写,shader在运行时编译,链接,最终在GPU上执行采样操作。
OpenGL中有两种shader:

  1. vertex shader(顶点着色器):vertex shader在每个顶点上都执行执行一次,通过不同世界的坐标系转化定位顶点的最终位置。它可以数据给fragment shader,如纹理坐标、顶点坐标,变换矩阵等。
  2. fragment shader(片段着色器):fragment shader在每个像素上都会执行一次,通过插值确定像素的最终显示颜色。

创建shader

以下是两个简单的shader,用来说明GLSL的语法特点。

vertex shader:

//attribute 关键字用来描述传入shader的变量
attribute vec4 vertexPosition; // 需要从外部获取的4分量vector
attribute vec4 pixelColor; 
//varying 关键字用来描述从vertex shader传递给fragment shader的变量
//精度修饰符分为三种:highp, mediump, lowp
varying mediump vec4 finalPixelColor; //mediumP修饰代表中等精度,提高效率。

void main(void) { 
    finalPixelColor = pixelColor; // 将pixelColor的值通过finalPixelColor传递给fragment shader
    gl_Position = vertexPosition; // gl_Position是vertex shader的内建变量,gl_Position中的顶点值最终输出到渲染管线中
}

fragment shader:

varying mediump vec4 finalPixelColor; 

void main(void) { 
    gl_FragColor = finalPixelColor; // gl_FragColor是fragment shader的内建变量,gl_FragColor中的像素值最终输出到渲染管线中
}
}

使用shader

shader在运行时完成编译、链接,是在GPU上执行的小程序,以下是shader编译、链接的过程,为了阅读方便,省略了调试异常情况的判断和调试log输出。

//编译shader函数
- (GLuint)compileShader:(NSString*)shaderName withType:(GLenum)shaderType {
    // 读取shader文件的内容为字符串
    NSString* shaderPath = [[NSBundle mainBundle] pathForResource:shaderName 
        ofType:@"glsl"];
    NSError* error;
    NSString* shaderString = [NSString stringWithContentsOfFile:shaderPath 
        encoding:NSUTF8StringEncoding error:&error];
    if (!shaderString) {
        NSLog(@"Error loading shader: %@", error.localizedDescription);
        exit(1);
    }
    // 创建shader对象,返回其引用
    GLuint shaderHandle = glCreateShader(shaderType);    
    // 获取C字符串,作为源代码传给OpenGL
    const char * shaderStringUTF8 = [shaderString UTF8String];    
    int shaderStringLength = [shaderString length];
    glShaderSource(shaderHandle, 1, &shaderStringUTF8, &shaderStringLength);
    // 运行时编译shader
    glCompileShader(shaderHandle);
    return shaderHandle;
}
//编译、链接shader
-(void)configuerShader{
    // 创建并编译shader
    GLuint vertexShader = [self compileShader:@"vertexShader" 
        withType:GL_VERTEX_SHADER];
    GLuint fragmentShader = [self compileShader:@"fragmentShader" 
        withType:GL_FRAGMENT_SHADER];
    //创建一个程序对象,返回其引用
    _programHandle = glCreateProgram();
    //将两个shader绑定到程序对象, 不需要时可以使用glDetachShader解绑
    glAttachShader(programHandle, vertexShader);
    glAttachShader(programHandle, fragmentShader);
    //链接两个shader
    glLinkProgram(_programHandle);
    //选择创建的程序对象为当前使用的程序,类似setCurrentContext, 不需要时使用glDeleteProgram删除
    glUseProgram(_programHandle);
  }
-(void)configuerSlot{
   //获取shader中attribute变量的引用
   _vertexPosition = glGetAttribLocation(programHandle, "vertexPosition");
   _pixelColor = glGetAttribLocation(programHandle, "pixelColor");
   //启用attribute变量,使其对GPU可见,默认为关闭
   glEnableVertexAttribArray(_vertexPosition);
   glEnableVertexAttribArray(_pixelColor);
 }

-(void)initOpenGl{
  [self configuerShader];
  [self coniigureSlot];
}

使用OpenGL绘制一个简单的矩形

以上内容介绍了OpenGL的基本数据结构,现在先来绘制一个简单的矩形

初始化

现在需要给OpenGL提供attribute变量值与顶点数据。顶点数据用来提供绘制时的几何信息。OpenGL中只能绘制三角形,三角形保证了其内部像素都在同一个平面。要绘制复杂的几何图形,可以用三角形组合的方式实现。
顶点数据使用VBO(vertex buffer object)来传递给GPU。

初始化VBO

OpenGL需要有两种VBO来确定几何图形,vertex VBO提供顶点本身,index VBO提供三角形所使用的顶点的index序列。这样保证了显存中存储的顶点数据是唯一的,不会浪费资源。VBO中存储着CPU传给GPU的数据,存储在显存里,在执行大量重复的绘制操作时,可以提高效率。

初始化attribute变量

之前创建的shader中,有两个attribute变量,需要使用glVertexAttribPointer输入给shader。

//我们需要在一个矩形中绘制图像,需要两个三角形模拟,所以需要四个顶点,索引数组说明了两个三角形顶点组成。
//默认情况下,OpenGL 的Viewport左下角顶点为(-1,-1),右上角顶点为(1,1)。
const float vertices[] = {
 1, -1, 0,//index 0
 1,  1, 0,//index 1
-1,  1, 0,//index 2
-1, -1, 0 //index 3
}
const GLubyte Indices[] = {
     0, 1, 2,
     2, 3, 0
};

- (void)setupVBOs {
    //顶点VBO
    GLuint vertexBuffer;
    glGenBuffers(1, &vertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    //将顶点坐标写入顶点VBO
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), Vertices, GL_STATIC_DRAW);
    //索引VBO
    GLuint indexBuffer;
    glGenBuffers(1, &indexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
    //将顶点索引数据写入索引VBO
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW);
  }
-(void)feedAttributeSlot{
    //由于使用了VBO,所以最后一个参数传数据在VBO中的偏移量,这点需要注意
    glVertexAttribPointer(_vertexPosition, 3, GL_FLOAT, GL_FALSE, sizeof(float)*3, 0);
    //没有使用VBO,直接传指针给函数,
    float blueColor[] = {0, 0, 1, 0};
    glVertexAttribPointer(_pixelColor, 4, GL_FLOAT, GL_FALSE, 0, blueColor);
  }

注意此时写入VBO的只是一些二进制的数据,需要在读取数据是,给出数据类型才能正确读取。

绘制

- (void)render {
    //绘制黑色背景
    glClearColor(0, 0, 0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    //创建一个OpenGL绘制的窗口
    glViewport(0, 0, self.frame.size.width, self.frame.size.height);
    //使用顶点索引,绘制图形。调用函数后,vertex shader会在每个顶点执行一遍,确定顶点信息。fragment shader会在每个像素执行一遍,确定像素颜色。
    //在使用VBO的情况下,最后一个参数传0
    glDrawElements(GL_TRIANGLES, sizeof(Indices)/sizeof(Indices[0]), 
        GL_UNSIGNED_BYTE, 0);
    //EACAGLContext 渲染OpenGL绘制好的图像到EACAGLLayer
    [_context presentRenderbuffer:GL_RENDERBUFFER];
}

由于shader中pixelColor变量类型是varying类型,在处理未知值是,会自动插值,默认为线性插值。如果对shader的GLSL语法不熟悉,可以看这篇文章。

所以,当顶点之间的颜色不同是,fragment shader在处理图形内部的像素后会返回一个根据顶点插值后的数值,整个图形是渐变色的。

这里因为四个顶点都设置为了蓝色,所以绘制出来是一个蓝色的矩形。

(添加图)

使用OpenGL绘制YUV数据

以上内容简单介绍了如何使用OpenGL绘制,现在重点如何使用OpenGL绘制YUV数据。
YUV是一种颜色编码格式,常用的格式有YUV444,YUV422P,YUV420P,YUV420SP等。
本文主要研究YUV420PI420YUV420SPNV12

纹理

我们需要将YUV数据纹理的方式加载到OpenGL,再将纹理贴到之前创建矩形上,完成绘制。
将每个顶点赋予一个纹理坐标,OpenGL会根据纹理坐标插值得到图形内部的像素值。OpenGL的纹理坐标系是归一化的,取值范围是0 - 1,左下角是原点。


三角形贴上纹理需要的纹理坐标

纹理目标、纹理对象、纹理单元

  1. 纹理目标是显卡的软件接口中定义的句柄,指向要进行当前操作的显存。
  2. 纹理对象是我们创建的用来存储纹理的显存,在实际使用过程中使用的是创建后返回的ID。
  3. 纹理单元是显卡中所有的可用于在shader中进行纹理采样的显存,数量与显卡类型相关,至少16个。在激活某个纹理单元后,纹理目标就该纹理单元,默认激活的是GL_TEXTURE0。

可以这么想象,纹理目标是转轮手枪正对弹膛的单孔,纹理对象就是子弹,纹理单元是手枪的六个弹孔。下面用代码说明它们之间的关系。

//创建一个纹理对象数组,数组里是纹理对象的ID
GLuint texture[3];
//创建纹理对象,第一个参数是要创建的数量,第二个参数是数组的基址
glGenTextures(3, &texture);
//激活GL_TEXTURE0这个纹理单元,用于之后的纹理采样
glActiveTexture(GL_TEXTURE0);
//绑定纹理对象texture[0]到纹理目标GL_TEXTURE_2D,接下来对纹理目标的操作都发生在此对象上
glBindTexture(GL_TEXTURE_2D, texture[0]);
//创建图像,采样工作在GL_TEXTURE0中完成,图像数据存储在GL_TEXTURE_2D绑定的对象,即texture[0]中。
glTexImage(GL_TEXTURE_2D, ...);
//解除绑定,此时再对GL_TEXTURE_2D不会影响到texture[0],texture[0]的内存不会回收。
glBindTexture(GL_TEXTURE_2D, 0);
//可以不断创建新的纹理对象,直到显存耗净

修改shader

之前创建的简单shader现在要修改代码,实现对YUV数据的绘制。如果对GLSL语法与YUV不熟悉,可以看OpenGL的着色语言:GLSLYUV颜色编码解析

//vertex shader
ttribute vec4 position;
attribute mediump vec2 textureCoordinate;//要获取的纹理坐标
varying mediump vec2 coordinate;//传递给fragm shader的纹理坐标,会自动插值

void main(void) { 
    gl_Position = vertexPosition; 
    coordinate = textureCoordinate;
}
//fragment shader
precision mediump float;

uniform sampler2D SamplerY;//sample2D的常量,用来获取I420数据的Y平面数据
uniform sampler2D SamplerU;//U平面
uniform sampler2D SamplerV;//V平面

uniform sampler2D SamplerNV12_Y;//NV12数据的Y平面
uniform sampler2D SamplerNV12_UV;//NV12数据的UV平面

varying highp vec2 coordinate;//纹理坐标

uniform int yuvType;//0 代表 I420, 1 代表 NV12

//用来做YUV --> RGB 的变换矩阵
const vec3 delyuv = vec3(-0.0/255.0,-128.0/255.0,-128.0/255.0);
const vec3 matYUVRGB1 = vec3(1.0,0.0,1.402);
const vec3 matYUVRGB2 = vec3(1.0,-0.344,-0.714);
const vec3 matYUVRGB3 = vec3(1.0,1.772,0.0);

void main()
{
    vec3 CurResult;
    highp vec3 yuv;

    if (yuvType == 0){
        yuv.x = texture2D(SamplerY, coordinate).r;//因为是YUV的一个平面,所以采样后的r,g,b,a这四个参数的数值是一样的
        yuv.y = texture2D(SamplerU, coordinate).r;
        yuv.z = texture2D(SamplerV, coordinate).r;
    }
    else{
        yuv.x = texture2D(SamplerY, coordinate).r;
        yuv.y = texture2D(SamplerUV, coordinate).r;//因为NV12是2平面的,对于UV平面,在加载纹理时,会指定格式,让U值存在r,g,b中,V值存在a中。
        yuv.z = texture2D(SamplerUV, coordinate).a;//这里会在下面解释
    }

    yuv += delyuv;//读取值得范围是0-255,读取时要-128回归原值
    //用数量积来模拟矩阵变换,转换成RGB值
    CurResult.x = dot(yuv,matYUVRGB1);
    CurResult.y = dot(yuv,matYUVRGB2);
    CurResult.z = dot(yuv,matYUVRGB3);
    //输出像素值给光栅器
    gl_FragColor = vec4(CurResult.rgb, 1);
}

加载YUV数据到纹理对象

现在有了可以处理YUV数据的shader,我们需要加载YUV数据,来让OpenGL完成绘制。

//创建纹理对象,需要3个纹理对象来获取不同平面的数据
-(void)setupTexture{
    _planarTextureHandles = (GLuint *)malloc(3*sizeof(GLuint));
    glGenTextures(3, _planarTextureHandles);
}
-(void)feedTextureWithImageData:(Byte*)imageData imageSize:(CGSize)imageSize type:(NSInteger)type{
    //根据YUV编码的特点,获得不同平面的基址
    Byte * yPlane =  imageData;
    Byte * uPlane =  imageData + imageSize.width*imageSize.height;
    Byte * vPlane =  imageData + imageSize.width*imageSize.height * 5 / 4;
    if (type == 0) {
        [self textureYUV:yPlane widthType:imageSize.width heightType:imageSize.height index:0];
        [self textureYUV:uPlane widthType:imageSize.width/2 heightType:imageSize.height/2 index:1];
        [self textureYUV:vPlane widthType:imageSize.width/2 heightType:imageSize.height/2 index:2];
    }else{
        [self textureYUV:yPlane widthType:imageSize.width heightType:imageSize.height index:0];
        [self textureNV12:uPlane widthType:imageSize.width/2 heightType:imageSize.height/2 index:1];
    }
}
- (void) textureYUV: (Byte*)imageData widthType: (int) width heightType: (int) height index: (int) index
{
    //将纹理对象绑定到纹理目标
    glBindTexture(GL_TEXTURE_2D, _planarTextureHandles[index]);
    //设置放大和缩小时,纹理的过滤选项为:线性过滤
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
    //设置纹理X,Y轴的纹理环绕选项为:边缘像素延伸
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    //加载图像数据到纹理,GL_LUMINANCE指明了图像数据的像素格式为只有亮度,虽然第三个和第七个参数都使用了GL_LUMINANCE,
    //但意义是不一样的,前者指明了纹理对象的颜色分量成分,后者指明了图像数据的像素格式
    //获得纹理对象后,其每个像素的r,g,b,a值都为相同,为加载图像的像素亮度,在这里就是YUV某一平面的分量值
    glTexImage2D( GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, imageData );
    //解绑
    glBindTexture(GL_TEXTURE_2D, 0);
}

CADisplayLink定时绘制

现在已经能够将YUV数据加载到纹理对象了,下一步来改造render方法,将其绘制到屏幕上。可以用CADisplayLink定时调用render方法,可以根据屏幕刷新频率来控制YUV视频流的帧率。

- (void)render {
    //绘制黑色背景
    glClearColor(0, 0, 0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    //获取平面的scale
    CGFloat scale = [[UIScreen mainScreen] scale];
    CGFloat width = _frame.size.width*scale;
    CGFloat height = _frame.size.height*scale;
    //创建一个OpenGL绘制的窗口
    glViewport(0, 0,width,height);
    [self drawTexture];
    //EACAGLContext 渲染OpenGL绘制好的图像到EACAGLLayer
    [_context presentRenderbuffer:GL_RENDERBUFFER];
} 
//fragment shader的sample数组
GLint sampleHandle[3];
//绘制纹理
- (void) drawTexture{
    //传纹理坐标给fragment shader
    glVertexAttribPointer([AVGLShareInstance shareInstance].texCoordAttributeLocation, 2, GL_FLOAT, GL_FALSE,
                          sizeof(Vertex), (void*)offsetof(Vertex, TexCoord));
    glEnableVertexAttribArray([AVGLShareInstance shareInstance].texCoordAttributeLocation);
    //传纹理的像素格式给fragment shader
    GLint yuvType = glGetUniformLocation(_programHandle, "yuvType");
    glUniform1i([AVGLShareInstance shareInstance].drawTypeUniform, yuvType);
    //type: 0是I420, 1是NV12
    int planarCount = 0;
    if (type == 0) {
        planarCount = 3;//I420有3个平面
        sampleHandle[1] = glGetUniformLocation(_programHandle, "samplerY");
        sampleHandle[2] = glGetUniformLocation(_programHandle, "samplerU");
        sampleHandle[3] = glGetUniformLocation(_programHandle, "samplerV");
    }else{
        planarCount = 2;//NV12有两个平面
        sampleHandle[1] = glGetUniformLocation(_programHandle, "SamplerNV12_Y");
        sampleHandle[2] = glGetUniformLocation(_programHandle, "SamplerNV12_UV");
    }
        for (int i=0; i<planarCount; i++){
            glActiveTexture(GL_TEXTURE0+i);
            glBindTexture(GL_TEXTURE_2D, _planarTextureHandles[i]);
            glUniform1i(sampleHandle[i], i);
        }
    //绘制函数,使用三角形作为图元构造要绘制的几何图形,由于顶点的indexs使用了VBO,所以最后一个参数传0
    //调用这个函数后,vertex shader先在每个顶点执行一次,之后fragment shader在每个像素执行一次,绘制后的图像存储在render buffer中。
    glDrawElements(GL_TRIANGLES, 6,GL_UNSIGNED_BYTE, 0);
}

可以想象的应用场景

使用OpenGL绘制视频,是实现简单AR最简单的方式,也可以根据业务来对视频播放做进一步的个性定制,比如动态打码,贴纸等。


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

iOS OpenGL渲染YUV数据 的相关文章

  • 在 OpenGL 着色器中检测 NaN 的最佳方法

    今天早上我遇到了一个似乎神秘的错误 我很幸运能够很快找到解决方案 我除以计数器以生成片段着色器内部的平均值 当然 当计数器为零时 所得的颜色值变为 NaN 在混合过程中 NVidia 优雅地将 NaN 视为 0 值 但 Intel 没有这样
  • 使用帧缓冲区将深度缓冲区渲染为纹理

    我正在使用 JOGL 但这个问题通常适用于 OpenGL 似乎也有类似的问题 但它们要么是针对 GLSL 代码 与复制帧缓冲区的内容有关 要么是一般建议 使用帧缓冲区对象而不是glCopyTexSubImage2D Question 我正在
  • 从活动顶点数组生成平滑法线

    我正在尝试通过挂钩 OpenGl 调用来破解和修改旧版 opengl 固定管道游戏的多个渲染功能 而我当前的任务是实现着色器照明 我已经创建了一个适当的着色器程序 可以正确照亮大部分对象 但该游戏的地形是在没有提供正常数据的情况下绘制的 游
  • 模拟绘画应用的笔触

    我正在尝试编写一个应用程序 可用于使用模拟笔触创建看起来像绘画的图片 是否有任何好的资源可以提供模拟笔触的简单方法 例如 给定用户拖动鼠标经过的鼠标位置列表 画笔宽度和画笔纹理 如何确定要在画布上绘制的内容 我尝试将画笔纹理倾斜到鼠标移动的
  • 着色器/矩阵问题 - 看不到对象

    我试图在屏幕上放置一个立方体并点亮它 我想要在立方体上添加 phong 阴影 当我运行代码时 我可以看到背景图像 但看不到立方体 我相当确定立方体本身是正确的 因为我已经设法用纯色着色器显示它 我已经设法编译着色器程序 但我根本看不到立方体
  • OpenGL NURBS 曲面

    我正在学习 OpenGL 我想要一个中间有轻微驼峰的表面 我目前正在使用这段代码 但我不确定如何调整 ctrl 点以使其达到我想要的方式 它目前就像 我想要这样的 我不完全确定我应该使用哪些控制点 并且我对其工作原理感到困惑 include
  • 将glm四元数转换为旋转矩阵并与opengl一起使用

    所以我将对象的方向存储在 glm fquat 中 我想用它来旋转我的模型 我怎么做 我试过这个 glPushMatrix glTranslatef position x position y position z glMultMatrixf
  • 使用 openGL、SOIL 加载图像

    我尝试了很多使用 SOIL 在 openGL 中加载和显示图像的示例 运行下面的源代码时 它仅显示一个没有图像的白色四边形 我尝试打开一个名为 foto 的图像 我将图像文件放在程序的文件夹中 bool keyStates new bool
  • 使用 Opengl 绘制立方体 3D

    我想使用 OpenGL 绘制 3D 立方体这是我的代码如何纠正错误 float ver 8 3 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
  • 哪个对缓存最友好?

    我试图很好地掌握面向数据的设计以及如何在考虑缓存的情况下进行最佳编程 基本上有两种情况我无法完全确定哪个更好以及为什么 是拥有一个对象向量更好 还是拥有对象原子数据的多个向量更好 A 对象向量示例 struct A GLsizei mInd
  • 在 OpenGL 中,为什么 glVertexAttribPointer 要求“指针”参数以 void* 形式传入?

    规格为glVertexAttribPointer如下 void glVertexAttribPointer GLuint index GLint size GLenum type GLboolean normalized GLsizei s
  • glutPostRedisplay 不在循环内工作

    我试图让一个人在 y 轴上跳跃 所以我使用 2 秒的循环 第一秒它应该向下移动并弯曲膝盖 第二秒它应该向上移动 然后在起始位置完成 现在我刚刚开始让这个人在第一秒内跪下并弯曲膝盖 我还没有编写动画的其余部分 问题是 glutPostRedi
  • openGL转png

    我正在尝试将包含大量纹理 没有移动 的 openGL 编辑 我画的卡片 thx unwind 转换为一个 PNG 文件 我可以在框架的另一部分中使用该文件我正在与 有 C 库可以做到这一点吗 thanks 如果您的意思只是 获取由 Open
  • 不理解 gluOrtho2D 函数

    我不能做什么gluOrtho2D 函数是做什么的 是否将原点固定在 OpenGL 窗口上的某个特定点或其他位置 这是因为gluOrtho2D 1 1 1 1 将原点固定在窗口的中间 如果它在某个时刻没有修复原点 那么有什么方法可以修复原点
  • 如何安装适用于 Windows C++ 的最新版本 OpenGL?

    我正在使用 Visual Studio 2010 运行 Windows 7 包含的 OpenGL 版本 include 是版本 1 1 我希望使用合理的当前版本 某种版本 3 或 4 我需要做什么才能达到该状态 OpenGL SDK 页面位
  • 将像素传递给 glTexImage2D() 后会发生什么?

    例如 如果我创建一个像素数组 如下所示 int getPixels int pixels new int 10 pixels 0 1 pixels 1 0 pixels 1 1 etc glTexImage2D getPixels glTe
  • 对齐坐标系

    Let s say I have 2 coordinate systems as it is shown in image attached 如何对齐这个坐标系 我知道我需要将第二个坐标系围绕 X 平移 180 度 然后将其平移到第一个坐标
  • 如果我用opengl绘图的话SDL Renderer就没用了吗?

    我正在学习 SDL2 但我也在使用使用 OpenGL 调用的 imgui 库 从我在网上各种博客上读到的内容来看 我无法轻松混合 SDL2 渲染器和 opengl 调用 我要么使用其中之一 要么使用另一个 我读过的大多数教程都使用渲染器 所
  • 为什么OpenGL使用float而不是double? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 在 Linux 上运行我自己的程序的权限被拒绝? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我有Ubuntu 9 4 我已经构建了程序 一些基本的 OpenGL 该程序只是制作一个旋转的正方形 然后运行它并 sh blabla p

随机推荐

  • 下载文件获取的contentLength 的值是-1的问题

    项目中加了个下载方法 获取的响应中的contentlength值总是 1 查看源码可以看到 用这个int类型的获取的当length大于Int的最大值 2147483647 就返回 1 我需要下载的文件确实大于这个值了 查看源码还有别的获取l
  • 数据库系统-第二章关系数据库 复习

    关系操作 关系数据库基础 关系代数 这个 的写法是错的 因为不存在一个元组的cno既等于c2又等于c4
  • UE4 性能优化方法(工具篇)

    官方文档链接 https docs unrealengine com latest CHN Engine Performance index html 本文依据UE4官方文档以及官方博客等总结而来 可能不全面 后面会陆续添加 内置工具的详细
  • 将Servlet中的ResultSet显示到Jsp页面

    Servlet从数据库中拿到结果以后 对用户来说 他们并不关心你怎么拿的 恰恰最重要的是 他们要看到信息展示在网页上 那这个该怎么实现 言归正传 拿到数据以后 想要把数据展示到Jsp页面上 这样办 跳转 转发和重定看大佬文章 清晰又明白 转
  • vscode连接服务器不用每次都输入密码

    1 首先在自己的本地生成公钥和密钥 git bash 输入以下命令 ssh keygen 然后一路回车 就会在自己用户 ssh文件夹下生成一对密钥 生成的公钥和密钥默认放在 ssh文件夹 我的是 2 修改本地的配置文件 添加下面这行属性到配
  • python学习笔记---IO编程【廖雪峰】

    IO编程 IO在计算机中指Input Output 也就是输入和输出 由于程序和运行时数据是在内存中驻留 由CPU这个超快的计算核心来执行 涉及到数据交换的地方 通常是磁盘 网络等 就需要IO接口 IO编程中 Stream 流 是一个很重要
  • linux中的阻塞机制及等待队列

    阻塞与非阻塞是设备访问的两种方式 驱动程序需要提供阻塞 等待队列 中断 和非阻塞方式 轮询 异步通知 访问设备 在写阻塞与非阻塞的驱动程序时 经常用到等待队列 一 阻塞与非阻塞 阻塞调用是没有获得资源则挂起进程 被挂起的进程进入休眠状态 调
  • 输出数组中最大和最小的元素值及其下标

    设计完整的程序实现以下功能 一个数组有10个元素 例如 1 8 10 2 5 0 7 15 4 5 利用指针作为函数参数 输出数组中最大和最小的元素值及其下标 include
  • TOPP问题(Time-Optimal Path Parameterization)详细解析(附代码)

    题目来源 深蓝学院课程 机器人中的数值优化 主讲 汪哲培博士 最后大作业 参考资料 课程ppt与视频 助教和大佬的提示和讨论 Verscheure D et al Time optimal path tracking for robots
  • java List的contains和remove方法,底层依赖的的对象的equals

    实例 新建一个Person类 像List中添加Person 进行contains和remove方法的测试 Person类 name和age两个属性 但是没有重写equals方法 public class Person private Str
  • Vue2中后台使用dhtmlx-gantt插件实现复杂甘特图

    在工作中由于业务的复杂性 需要使用dhtmlx gantt来实现复杂表格 以下是甘特图的实现以及一些配置描述 由于官方文档是英文的 所以对英文不好的不太友好 官方文档 Gantt API Gantt Docs 相关配置 1 一行需要展示多条
  • Spring框架 AOP

    AOP 面向切面编程 是一种新的方法论 是对传统OOP 面向对象编程 的补充 AOP编程时 扔然需要定义公共功能 但可以明确的定义这个功能在哪里 以什么方式应用 并且不必修改受影响的类 这样一来横切关注点就被模块化到特殊的对象 切面 里 a
  • vscode 所有的默认配置项

    文档 官网 setting json 快速打开 使用快捷键 Ctrl Shift P 然后搜索setting 首选项 打开默认设置 json 这个打开的是defaultSettings json文件 可以在你的默认配置中看到这些 然后自己配
  • 对sql注入的一些理解

    前言 第一个接触的漏洞就是sql注入 一个危害很大到现在都还在流行的漏洞 利用sql注入可以对网站进行脱库 也可以写入shell控制服务器 假期正好有时间 再一次梳理关于sql注入的一些知识 自身理解 我对这个漏洞的理解就是前端的数据可以直
  • kubeadm安装

    一 硬件环境准备 三台机器 计划为 一台master 两台node 序号 ip 系统版本 hostname 配置 节点类型 1 192 168 137 61 CentOS 7 4 1611 Core master61 2核2G Master
  • 主流的6个Go语言Web框架

    GO 语言爱好者的最佳Web框架 如果你是自己写一个小应用程序 那你可能不需要Web框架 但是如果你要做产品 那么你肯定需要一个好的框架 如果你认为你有相应的知识和经验 你会自己编写所有的这些代码么 你有时间找到一个产品级的外部包来完成工作
  • google各国网址

    google各国网址 巴西 www google com br 瑞士 www google ch 荷兰 www google nl 澳大利亚 www google com au 印度 www google co in 罗马尼亚 www go
  • es每次结果不一样_Elasticsearch 分页坑之---评分一致导致数错乱

    1 背景介绍 最近搞es搜索 match查询默认按照评分排序 发现有一部分数据评分一致 一开始也没注意 客户端调用分页的时候 突然发现数据重复错乱很严重 挖槽顿时觉得 挖槽怎么那么坑 from size 做分页 每次都是重新加载 所以评分一
  • react不能用@引用文件

    方法一 步骤 1 删除node models 步骤 2 重新cnpm install 如果cnpm install时右上角出现eslint 省略号是因为记不清了 点击选择忽略 可能会解决
  • iOS OpenGL渲染YUV数据

    链接 http www jianshu com p 39cde80d60e2 本文主要介绍使用OpenGL ES来渲染I420 YUV420P NV12 YUV420SP 的方法 关于YUV的知识 可以看这里 YUV颜色编码解析 同样会用到