Shadow Map 实现极其细节

这里不介绍算法原理,只说说在实现过程中遇到的问题,以及背后的原因。开发环境:opengl 2.0  glsl 1.0。

第一个问题:产生深度纹理。

在opengl中每一次离屏渲染需要向opengl提供一个renderframe,一个renderframe包含一个texture和一个renderbuffer.texture是一个存储特定数据的内存区,可以存储颜色,深度以及模版。renderbuffer目前不太清楚。

具体代码如下:

    glGenFramebuffers(1, &frameBuff) ;
    glBindFramebuffer(GL_FRAMEBUFFER, frameBuff) ;

    glGenTextures(1, &depthTxe) ;
    glBindTexture(GL_TEXTURE_2D, depthTxe) ;
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16 , mapWidth, mapHight, 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTxe, 0);
    glDrawBuffer(GL_NONE) ;
    glReadBuffer(GL_NONE) ;
     GLenum result = glCheckFramebufferStatus(GL_FRAMEBUFFER);
        if( result == GL_FRAMEBUFFER_COMPLETE) {
        cout << "Framebuffer is complete.\n" << endl ;
    } else {
        cout <<"Framebuffer is not complete.\n" << endl ;
    }

这里需要注意的是,一定要加    glDrawBuffer(GL_NONE) ;  glReadBuffer(GL_NONE) 。分别告诉opengl没有缓冲区接受或者读取颜色数据。因为我们需要的是深度数据。如果没有这两句,那么返回的result是not complete。

曾经尝试过使用 glTexImage2D 中使用 GL_RGB 而不是 GL_DEPTH_COMPONENT16 相应的下面的 glFramebufferTexture2D 也要使用 GL_COLOR_ATTACHMENT。在fragment shader中将片元的深度值写入color中。但是这样有个问题,就是精度不够,虽然数据是对的,精度在Shadow Map中有着极其重要的位置,稍后介绍。

问题二:各种坐标系的转化

这里涉及到两个变化过程。第一个是从物体坐标系-》世界坐标系-》灯光坐标系-》裁剪坐标系(齐次坐标系)-》cvv坐标系(透视除法后得到)。

另一个是物体坐标系-》世界坐标系-》摄像机坐标系-》裁剪坐标系(齐次坐标系)-》cvv坐标系(透视除法后得到)。

具体vertex shader 如下

varying vec3 normal ;
varying vec4 lightVertex ;
varying vec4 color ;
varying vec4 worldCoord ;
uniform mat4 lightProj;
uniform mat4 lightView;
const  mat4 biasMatrix = mat4(0.5 , 0.0 , 0.0 , 0.0 ,
                       0.0 , 0.5 , 0.0 , 0.0 ,
                       0.0 , 0.0 , 0.5 , 0.0 ,
                       0.5 , 0.5 , 0.5 , 1.0 ) ;
void main()
{
     worldCoord = gl_ModelViewMatrix * gl_Vertex ;
     normal =  normalize(gl_NormalMatrix * gl_Normal);
    lightVertex = lightProj * lightView * worldCoord ;
    lightVertex = lightVertex / lightVertex.w ;
    lightVertex = biasMatrix * lightVertex ;
    //lightVertex = lightVertex / lightVertex.w ;
    gl_TexCoord[0] = gl_MultiTexCoord0 ;
    color = gl_Color ;
    gl_Position =  gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex ;
}

这里需要注意的是glsl中没有提供从物体坐标系到世界坐标系的转化,gl_ModelViewMatrix实现的是从物体到相机坐标系的转化,在本次shadow map是实现过程中将一直保证摄像机在世界坐标系的原点位置,方向指向(0,0,-1),向上方向为(0,1,0)。也就是说摄像机和世界坐标系重合。

注意到 biasMatrix,这里可以推知两点:

(1)glsl中是采用列主序的方式存放矩阵的,也就是数据先放完第一列再放第二列。

(2)glsl纹理的坐标被映射到[0,1]之间。用本次shadow map举例,从灯光坐标系到齐次坐标系,经过裁剪以及透视除法后,得到的x,y,z的范围是在[-1,1]之间的,构成一个规范立方体(cvv),那么x , y的值就是纹理在纹理内存中的位置,z就是伪深度,用于z-buffer。但是,opengl在渲染深度纹理时,将x,y,z映射到[0,1]之间,所以要用一个biasMatrix进行平移。

第三个问题:Shadow acne。

这个问题的原因如下图:

其中,斜线EF是物体表面,当一光柱照射到物体表面时,C点的深度值被写入shadow map中(这里有一个概念:texle , texture element,可以理解为一个像素)。但是,当从摄像机坐标系中看到D点时,通过坐标系转化到shadow map中 ,D点比C点的值远离光源。这样就造成了shadow acne。效果如下:

这时要加上一个偏移两bias,具体计算在fragment shader中,其中的m时具体情况具体调整的,时经验值。

问题四:阴影有锯齿。

如下图:

改进方法时使用多次采样,可以使用采样次sampler2Dshadow,内部会采样一个像素周围的几个像素。也可以使用本次代码中的poissonDisk进行优化。效果如下

这个poissonDisk(柏松盘采样)内容目前不太了解,貌似效果还不错。

学习心得:在碰到一个新技术时,最好把其最简单的部分实现,然后看看有什么问题,然后慢慢优化。如果不动手实现,很难体会资料上描述的问题的现象,更不用说问题产生的原因。

一下时全部代码:

#include <GLUT/GLUT.h>
#include <iostream>
#include "LoardShaderProgram.h"
#include "BW_READBMP.h"
using namespace std ;

struct COLOR
{
    float r , g , b, w ;
} ;
float rot = 0.1 ;
float transX = 0 ;
float transY = 0 ;
float transZ = 0 ;
GLint program ;
GLint lightPosLoc ;
GLint lightAmbientLoc ;
GLint lightLambertLoc ;
GLint lightProjLoc ;
GLint lightViewLoc ;
GLint mLoc ;
GLint ksLoc ;
GLint kdLoc ;
GLint kaLoc ;
GLint texLoc ;

GLint tex ;
GLint mapWidth =  1024;
GLint mapHight  = 1024;
GLuint depthTxe ;
GLuint frameBuff ;
GLfloat lightProj[16] ;
GLfloat lightView[16] ;
float alf  = 0 ;
float ks = 1;
float kd = 0.5 ;
float ka = 1.0 ;
float m = 0.0035410088 ;
COLOR lightPos ;
void DrawSence()
{
    glTranslated(0, 0, -8) ;
    glBegin(GL_QUADS);
    glColor3f(1, 0, 0) ;

    // ground
    //glNormal3f( 0.0f, 0.0f, 1.0f);
    glTexCoord2f(0.0f, 0.0f);
    glNormal3f(0, 1, 0) ;
    glVertex3f(4.0f, -2.0f, -4.0f);
    glNormal3f(0, 1, 0) ;
    glTexCoord2f(0.0f, 1.0f);
    glVertex3f( -4.0f, -2.0f, -4.0f);
    glNormal3f(0, 1, 0) ;
    glTexCoord2f(1.0f, 1.0f);
    glVertex3f( -4.0f,  -2.0f,  4.0f);
    glNormal3f(0, 1, 0) ;
    glTexCoord2f(1.0f, 0.0f);
    glVertex3f(4.0f,  -2.0f,  4.0f);

    glEnd() ;

    glTranslated(transX, transY , transZ) ;
    glRotatef(rot,1.0,0.0,1.0);
    glutSolidTeapot(1);
}
void ProcessKeyboard(unsigned char key,int x,int y)
{
    if (key == ‘x‘ || key == ‘X‘)
    {
        rot += 0.8;
    }
    if (key == ‘c‘ || key == ‘C‘)
    {
       m += 0.00001 ;
    }
    if (key == ‘d‘ || key == ‘D‘)
    {
        m -= 0.00001 ;
    }
    if (key == ‘u‘ || key == ‘U‘)
    {
        transX += 0.3 ;
    }
    if (key == ‘i‘ || key == ‘I‘)
    {
        transX -= 0.3 ;
    }
    if (key == ‘j‘ || key == ‘J‘)
    {
        transY += 0.3 ;
    }
    if (key == ‘k‘ || key == ‘K‘)
    {
        transY -= 0.3 ;
    }
    if (key == ‘o‘ || key == ‘O‘)
    {
        transZ += 0.3 ;
    }
    if (key == ‘p‘ || key == ‘P‘)
    {
        transZ -= 0.3 ;
    }

    glutPostRedisplay() ;
}

void ReSizeScene(int width , int height)
{
    if( height==0 )
    {
        height=1;
    }//if

    //设置视口
    glViewport(0,0,width,height);
    //设置透视矩阵
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);
    //开始对模型矩阵进行操作
    glMatrixMode(GL_MODELVIEW);
    //复位
    glLoadIdentity();
}
void GenDepthMap()
{
    lightPos.r = 5; lightPos.g = 5; lightPos.b = -5; lightPos.w = 1;
    glBindFramebuffer(GL_FRAMEBUFFER, frameBuff) ;
   glDrawBuffer(GL_NONE) ;
    glViewport(0, 0, mapWidth, mapHight) ;
    glMatrixMode(GL_PROJECTION) ;
    glLoadIdentity() ;
    gluPerspective(90, 4.0/3, 0.1, 100) ;
    glGetFloatv(GL_PROJECTION_MATRIX, lightProj) ;
    glMatrixMode(GL_MODELVIEW) ;
    glLoadIdentity() ;
    gluLookAt(lightPos.r, lightPos.g, lightPos.b, 0, 0, -10, 0, 1, 0) ;
    glGetFloatv(GL_MODELVIEW_MATRIX,lightView) ;
    glDisable(GL_TEXTURE_2D) ;
   // glUseProgram(shadowPro) ;
    glClearColor(1.0, 1.0, 1.0, 1.0 );
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT) ;
    DrawSence() ;
    glUseProgram(0) ;
    glBindFramebuffer(GL_FRAMEBUFFER, 0) ;
    glDrawBuffer(GL_FRONT) ;
}
void ShowSence()
{
    ReSizeScene(400, 300) ;
    COLOR lightAmbient , lightLambert  ;
    COLOR CameralDir , CameralPos;
    lightAmbient.r =  0.1;lightAmbient.g = lightAmbient.b = 0.1; lightAmbient.w = 1 ;
    lightLambert.r = 1 ; lightLambert.g = lightLambert.b =  1 ;lightLambert.w = 1 ;

    CameralDir.r = 0 ;
    CameralDir.g = 0 ;
    CameralDir.b = -1 ;
    CameralDir.w = 0 ;

    CameralPos.r = 0 ;
    CameralPos.g = 0 ;
    CameralPos.b = 0 ;
    CameralPos.w = 1 ;
    ka = 1 ;
    kd = 0.7 ;
    ks = 0.4 ;
    tex = 0 ;
    glActiveTexture(GL_TEXTURE0) ;
    glBindTexture(GL_TEXTURE_2D, depthTxe) ;
    glUseProgram(program) ;
    glUniform4f(lightPosLoc, lightPos.r, lightPos.g, lightPos.b, lightPos.w) ;
    glUniform4f(lightAmbientLoc, lightAmbient.r, lightAmbient.g, lightAmbient.b, lightAmbient.w) ;
    glUniform4f(lightLambertLoc, lightLambert.r, lightLambert.g, lightLambert.b, lightLambert.w) ;
    glUniformMatrix4fv(lightProjLoc, 1, GL_FALSE, lightProj) ;
    glUniformMatrix4fv(lightViewLoc, 1, GL_FALSE, lightView) ;
    glUniform1f(mLoc, m) ;
    glUniform1f(kdLoc, kd) ;
    glUniform1f(ksLoc, ks) ;
    glUniform1f(kaLoc, ka) ;
    glUniform1i(texLoc , tex) ;
    gluLookAt(CameralPos.r, CameralPos.g , CameralPos.b, CameralDir.r, CameralDir.g , CameralDir.b, 0 ,1 , 0 ) ;
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT) ;
    DrawSence() ;
    glUseProgram(0) ;
    glFlush();
}
void display()
{
    GenDepthMap() ;
    ShowSence() ;
}

void InitScene()
{
    glClearColor(1.0, 1.0, 0.0, 1.0) ;
    glGenFramebuffers(1, &frameBuff) ;
    glBindFramebuffer(GL_FRAMEBUFFER, frameBuff) ;

    glGenTextures(1, &depthTxe) ;
    glBindTexture(GL_TEXTURE_2D, depthTxe) ;
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16 , mapWidth, mapHight, 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTxe, 0);
    glDrawBuffer(GL_NONE) ;
    glReadBuffer(GL_NONE) ;
    GLenum result = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    if (result == GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER)
    {
        cout << "frambuffer GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER " << endl ;
    }

    if( result == GL_FRAMEBUFFER_COMPLETE) {
        cout << "Framebuffer is complete.\n" << endl ;
    } else {
        cout <<"Framebuffer is not complete.\n" << endl ;
    }

    glBindFramebuffer(GL_FRAMEBUFFER, 0) ;
    glBindTexture(GL_TEXTURE_2D, 0) ;

    SHADERINFO shaderInfo[2] ;
    shaderInfo[0].shader_type = GL_VERTEX_SHADER ;
    shaderInfo[0].name = "ShadowMap.vertex" ;
    shaderInfo[1].shader_type = GL_FRAGMENT_SHADER ;
    shaderInfo[1].name = "ShadowMap.frag" ;
    program = BWLoadShaders(shaderInfo, 2) ;
    lightPosLoc = glGetUniformLocation(program, "lightPos") ;
    lightAmbientLoc = glGetUniformLocation(program, "lightAmbient") ;
    lightLambertLoc = glGetUniformLocation(program, "lightLambert") ;
    lightProjLoc = glGetUniformLocation(program, "lightProj") ;
    lightViewLoc = glGetUniformLocation(program, "lightView") ;
    mLoc  = glGetUniformLocation(program, "m") ;
    kdLoc = glGetUniformLocation(program, "kd") ;
    ksLoc = glGetUniformLocation(program, "ks") ;
    kaLoc = glGetUniformLocation(program, "ka") ;
    texLoc =  glGetUniformLocation(program, "tex") ;
    glEnable(GL_DEPTH_TEST) ;
    glDepthFunc(GL_LEQUAL);
    glShadeModel(GL_FLAT) ;

}

int main(int argc, char ** argv)
{
    glutInit(&argc, argv);
    glutInitWindowSize(800, 800);
    glutInitDisplayMode(GLUT_RGB|GLUT_DEPTH) ;
    glutCreateWindow("SHADERTEST");
    glutKeyboardFunc(&ProcessKeyboard) ;
    InitScene() ;
    glutDisplayFunc(display);
    glutMainLoop();
}

ShadowMap.vertex

varying vec3 normal ;
varying vec4 lightVertex ;
varying vec4 color ;
varying vec4 worldCoord ;
uniform mat4 lightProj;
uniform mat4 lightView;
uniform vec4 lightPos ;
uniform vec4 lightLambert ;
uniform vec4 lightAmbient ;

const  mat4 biasMatrix = mat4(0.5 , 0.0 , 0.0 , 0.0 ,
                       0.0 , 0.5 , 0.0 , 0.0 ,
                       0.0 , 0.0 , 0.5 , 0.0 ,
                       0.5 , 0.5 , 0.5 , 1.0 ) ;
void main()
{
     worldCoord = gl_ModelViewMatrix * gl_Vertex ;
     normal =  normalize(gl_NormalMatrix * gl_Normal);
    lightVertex = lightProj * lightView * worldCoord ;
    lightVertex = lightVertex / lightVertex.w ;
    lightVertex = biasMatrix * lightVertex ;
    //lightVertex = lightVertex / lightVertex.w ;
    gl_TexCoord[0] = gl_MultiTexCoord0 ;
    color = gl_Color ;
    gl_Position =  gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex ;
}

ShadowMap.frag

uniform sampler2D  tex ;
uniform vec4 lightPos ;
uniform vec4 lightLambert ;
uniform vec4 lightAmbient ;
uniform float m ;
uniform float ka ;
uniform float kd ;
uniform float ks ;
varying vec3 normal ;
varying vec4 lightVertex ;
varying vec4 color ;
varying vec4 worldCoord ;
void main()
{
    vec2 poissonDisk[4] ;
    poissonDisk[0] = vec2( -0.94201624, -0.39906216 ) ;
    poissonDisk[1] = vec2( 0.94558609, -0.76890725 ) ;
    poissonDisk[2] = vec2( -0.094184101, -0.92938870 );
    poissonDisk[3] = vec2( 0.34495938, 0.29387760 ) ;
    vec3 l = normalize(lightPos - worldCoord).xyz ;
    vec3 n = normalize(normal) ;
    vec3 v = normalize(-worldCoord).xyz ;
    float d = max(dot(n , l) , 0.0) ;
    float s = max(dot(normalize(v + l) , n) , 0.0)  ;
    float cosTheta = dot(n , l) ;
    float bias = m*tan(acos(cosTheta));
    float shadow = 1.0 ;
    //使用poissonDisk采样优化锯齿
    for (int i = 0 ; i < 4; ++i)
    {
        if ( texture2D( tex, lightVertex.xy + poissonDisk[i]/700.0 ).z  <  lightVertex.z-bias ){
            shadow -=0.2;
        }
    }
    //不使用优化
   /*float depth = texture2D(tex , lightVertex.xy).z ;

    if (depth + bias < lightVertex.z  )
    {
        shadow = 0.2 ;
    }*/
   // shadow = 0.5 ;
    vec4 finalColor = vec4(0.0 , 0.0 , 0.0 , 1.0) ;
    //finalColor *= shadow ;
    finalColor = (finalColor + ks * s * lightLambert + ka*lightAmbient  + kd * d * lightLambert)*shadow;
    gl_FragColor = finalColor ;
}
时间: 2024-08-03 19:26:37

Shadow Map 实现极其细节的相关文章

[工作积累] shadow map问题汇总

1.基本问题和相关 Common Techniques to Improve Shadow Depth Maps: https://msdn.microsoft.com/en-us/library/windows/desktop/ee416324(v=vs.85).aspx Cascaded Shadow Maps https://msdn.microsoft.com/en-us/library/windows/desktop/ee416307(v=vs.85).aspx Soft shadow

Shadow Map阴影贴图技术之探 【转】

这两天勉勉强强把一个shadowmap的demo做出来了.参考资料多,苦头可不少.Shadow Map技术是目前与Shadow Volume技术并行的传统阴影渲染技术,而且在游戏领域可谓占很大优势.本篇是第一辑.——ZwqXin.comShadow Map的原理很简单,但是实现起来到处是雷.当然这只是我的体会.恩,不过就是“从光源处看场景,那些看不见的区域全部都该是阴影”.很容易看出,与针对 特定模型的Shadow Volume不同,Shadow Map是针对场景的.这就是说,对一个光源应用一次

Shadow Map阴影贴图技术之探

这两天勉勉强强把一个shadowmap的demo做出来了.参考资料多,苦头可不少.Shadow Map技术是目前与Shadow Volume技术并行的传统阴影渲染技术,而且在游戏领域可谓占很大优势.本篇是第一辑.--ZwqXin.comShadow Map的原理很简单,但是实现起来到处是雷.当然这只是我的体会.恩,不过就是"从光源处看场景,那些看不见的区域全部都该是阴影".很容易看出,与针对特定模型的Shadow Volume不同,Shadow Map是针对场景的.这就是说,对一个光源

阴影映射(Shadow Map)的研究(二)

阴影映射(Shadow Map)的研究(二) 上一篇文章介绍了我对Z缓存的较为详细的研究.这里之所以对Ze求导函数,是因为的我们需要寻找它的变化曲线,从而找到极值点,这样就能够确定Ze相对于zw的疏密分布情况.幸运的是,我们找到的导函数是双曲函数,并且我们关心的的右侧是单调递增的. 蒋彩阳原创文章,首发地址:http://blog.csdn.net/gamesdev/article/details/44946763.欢迎同行前来探讨. 引出上一篇文章的结论,当 时,导函数取得最大值.但是在Zw∈

(转)Shadow Map &amp; Shadow Volume

转自:http://blog.csdn.net/hippig/article/details/7858574 shadow volume 这个术语几乎是随着 DOOM3 的发布而成为FPS 玩家和图形学爱好者谈论的对象的.虽然这个游戏还没有上市,但是凭借 John Carmack 的传奇经历以及 DOOM3发布的一些让人惊讶的预览图片,我们仍然有理由认为它将会是 2004 年最热门的 FPS 游戏之一. id software向来都不吝惜为了达到最好的图像效果而使用最先进的渲染技术,这曾经使得玩

Shadow Map 原理和改进 【转】

http://blog.csdn.net/ronintao/article/details/51649664 参考 1.Common Techniques to Improve Shadow Depth Maps 2.Tutorial 16 : Shadow mapping 3.Shadow Mapping 4.Shadow Mapping Algorithms 5.Shadow Map阴影贴图技术之探 6.Cascaded Shadow Maps 写在前面 之前已经很久没有再更新博客,上一篇已

阴影映射(Shadow Map)的研究(三)

阴影映射(Shadow Map)的研究(三) 最近为了自己制作的项目可是吃了不少苦头,这其中关键的一点就是想要实现阴影映射(Shadow Map).为了实现目标,我参考了网络上很多相关的资料,也看了一些案例,最终花了我一个月的时间将这个效果实现了. 阴影映射这样的效果,其实在即将发布的Qt 3D中已经有相关的介绍,KDAB中有一篇文章<Shadow Mappingin Qt3D 2.0>就在Qt 3D的框架上实现了阴影映射.不过当时这个效果是假定目标机器支持OpenGL 3.0规范的,目前大部

阴影映射(Shadow Map)的研究(四)

阴影映射(Shadow Map)的研究(四) 上一篇文章粗略地介绍了要实现OpenGL ES 2.0的阴影映射所需的知识难点,现在简略地说明一下:1.FBO:2.着色器:3.float的分拆以及组合.上篇文章虽然说已经成功地移植了来自Java编写的Android下阴影映射的效果,但这边采用的很大程度上是OpenGL原生代码编写的内容,接下来的目标是采用自Qt 5起就逐渐采用的Qt对OpenGL的封装类,用面向对象的思维来处理OpenGL对象,这样让代码更加优雅. 1.FBO 首先说一下FBO.在

阴影映射(Shadow Map)的研究(五)

阴影映射(Shadow Map)的研究(五) 我成功地将别人的例子加以改进,使用QOpenGLWidget作为渲染窗口,将阴影映射渲染了出来.目前可以确定的是,使用OpenGL ES 2.0作为渲染的接口要求,能够让目前绝大多数机器都能够顺利兼容,但是囿于渲染窗口,可能在某些平台上表现不好.如果移植到Qt Quick 2,这样能够支持的平台就更多了.现在我将这些接口统统使用Qt的方式实现了,移植到Qt Quick 2也很简单. 这里主要参考的是OpenGLUnderQML这个例子,自定义了一个Q