Android OpenGL绘制圆柱体

Modern OpenGL绘制圆柱体 本文主要介绍如何使用 C++ 生成圆柱几何体以及如何在 OpenGL 中绘制它。 1.绘制方法 由于我们无法绘制一个完美的圆形底面和圆柱体的弯曲侧面,我们只能通过将底面除以扇区(切片)来采样有限数量的点。因此,它在技术上是通过将这些采样点连接在一起来构建棱柱体。随着样本数量的增加,几何形状更接近于圆柱体。 假设一个圆柱体以原点为中心,半径为r,高度为h。圆柱体上的任意点 ( x, y, z ) 可以从具有相应扇形角θ的圆方程计算出来: 扇形角的范围是从 0 到 360 度。每个步骤的扇形角可以通过以下方 阅读详情

在之前矩形的项目中,矩形现在被放置在一个更好的角度,并且通过使用纹理,它的外观变得更加吸引人。但是,目前我们的矩形太单调了,这显然是不够的。许多应用程序通过合并简单的图形来构建更复杂的物体,我们即将学习如何进行这样的操作,本篇我们将学习如何构建立体图形,我们先从圆柱体开始。

在这里,我们还需要一个简便的方法来在场景中进行平移、旋转和移动。许多三维应用程序通过使用视图矩阵来实现这些功能;对视图矩阵所做的更改将影响整个场景,就像我们通过一个移动的相机观察事物一样。我们将添加一个视图矩阵,以使旋转和移动变得更加容易。

在本篇的开发计划中,我们将执行以下步骤:

  • 首先,我们将学习如何将三角形组织成三角形带和三角形扇,并将它们合并成一个单一的物体(圆柱体)。
  • 我们还将学习如何定义视图矩阵,并将其集成到我们的矩阵层次结构中。

完成这些后,我们不仅为我们的场景构建了一个圆柱体,而且只需一行代码就能移动整个场景。

构建圆柱体

1.如何构建圆柱体

要创建一个圆柱体,我们需要对圆柱体进行拆分,圆柱体可以被拆分为一个圆形的顶部和一个圆筒形的侧面。顶部的圆形我们可以通过三角形扇来实现,而在OpenGL中为了构建圆柱的侧面,我们可以使用三角形带(Triangle Strip)的概念。三角形带允许我们通过定义一系列的顶点来构建多个三角形,而无需重复定义共用的顶点。通过这种方式,我们可以高效地构建出圆柱体的侧面。

在OpenGL中,三角形扇(Triangle Fan)和三角形带是两种非常有用的技术,它们可以帮助我们以较少的顶点定义复杂的几何形状。例如,通过使用三角形扇,我们可以用六个顶点和四个三角形来构建一个矩形。三角形扇通过围绕一个中心点排列顶点来构建形状,而三角形带则像桥梁的横梁一样排列,每个新的三角形都共享前一个三角形的两个顶点。如下图所示:

正如三角形扇一样,一个三角形带的前三个顶点定义了第一个三角形。这之后的每个额外的顶点都定义了另外的一个三角形。为了使用三角形带定义这个圆柱体的侧面,我们只需要把这个带卷绕成一个管子,并确保最后两个顶点与最前面的两个顶点一致。

对Android来说,已经有很多合适的三维库了,从简单的开源封装二libgdx,再到比较高级的商业化框架,比如Unity3D。这些库能帮助你提高生产率,但只有当你对OpenGL、三维渲染,以及底层是如何把这些东西拼在一起的有了基本的理解后才能体会到;否则这些库没有任何意义,你也许感觉像使用黑箱魔法一样。举个例子,如Spring和Hibernate等框架使Java效率更高,但是如果都不知道Java是如何工作的,就开始使用它们,是不是为时尚早。

研究这些库有助于学习如何开发自己的组件。Java3D和jMonkeyEngine是Java桌面版上广泛使用的框架;因为它们的文档很容易从网上得到,它们是很好的学习起点。

2.添加几何图形类

为了制作圆柱体,我们首先需要确定所需的基本形状。可以通过一个三角形扇形作为顶部和一个三角形带作为侧面来构建。为了简化圆柱体的构建过程,我们计划创建一个名为“Geometry”的几何图形类,这个类将包含一些基本图形的定义,并且我们将使用一个名为ObjectBuilder的工具来实际构建这些形状。

接下来,我们将从创建几何物体类开始。在类内部,我们将添加必要的代码来定义和构建所需的几何形状。这个类将作为构建圆柱体的基础,使得整个过程更加系统化和高效。

class Point(var x: Float = 0f, var y: Float = 0f, var z: Float = 0f) {
    fun translateY(distance:Float):Point{
        return Point(x, y + distance, z)
    }
}

我们已经添加了一个类用来表示三维场景中的一个点,其中有一个辅助函数用于把这个点沿着y轴平移。我们也需要给圆一个定义;在 Point类后面加入如下代码:

class Circle(var center:Point,var radius:Float)

最后是给圆柱体一个定义:

class Cylinder(var center:Point,var radius:Float,var height:Float)

一个圆柱体就像一个扩展的圆,它有一个中心、一个半径和一个高度。

2.添加圆柱体构建器

我们现在可以开始写圆柱体构建器类了。创建一个名为“ObjectBuilder”的类。在类内部,以下面的代码作为开始:

class ObjectBuilder {
    private val FLOAT_PER_VERTEX = 3
    private var vertexData:FloatArray
    private var offset = 0
    
    private constructor(sizeInVertices:Int){
        vertexData = FloatArray(sizeInVertices*FLOAT_PER_VERTEX)
    }

目前为止,没有什么太复杂的。我们定义了一个常量用来表示一个顶点需要多少浮点数,一个数组用于保存这些顶点,以及一个变量用于记录数组中下一个顶点的位置。这个构造函数基于需要的顶点数量初始化了数组。

我们不久将定义一个静态方法用来生成圆柱体。这个静态方法会使用正确的大小创建一个新的ObjectBuilder实例,调用ObjectBuilder实例中的方法给vertexData添加顶点,并把生成的数据返回给调用者。

对于这个物体构建器应该怎样工作,我们给出了一些需求:

  • 调用者可以决定物体应该有多少个点。点越多,圆柱体看上去就越平滑。
  • 物体将被包含在一个浮点数组中。物体被创建后,调用者将有一个绑定到OpenGL的数组和一个绘制物体的命令。
  • 物体将以调用者指定的位置为中心,并平放在x-z平面上,换句话说,物体的顶部要指向正上方。

我们用一个计算圆柱体顶部顶点数量的方法作为开始:

    private fun sizeOfInCircleVertices(numPoints:Int):Int = 1+(numPoints+1)

一个圆柱体的顶部是一个用三角形扇构造的圆;它有一个顶点在圆心,围着圆的每个点都有一个顶点,并且围着圆的第一个顶点要重复两次才能使圆闭合。下面计算圆柱体侧面顶点的数量:

    private fun sizeOfInOpenCylinderVertices(numPoints:Int):Int = (numPoints+1)*2

一个圆柱体的侧面是一个卷起来的长方形,由一个三角形带构造,围着顶部圆的每个点都需要两个顶点,并且前两个顶点要重复两次才能使这个管闭合。

2.1 实现构建圆柱体

接下来我们就要真正开始构建圆柱体了,继续添加如下代码:

        fun createCylinder(cylinder: Cylinder,numPoints:Int):GeneratedData{
            var size = sizeOfInCircleVertices(numPoints)+ sizeOfInOpenCylinderVertices(numPoints)
            var builder  = ObjectBuilder(size)

            var circleTop = Circle(cylinder.center.translateY(cylinder.height/2),cylinder.radius)

            builder.appendCircle(circleTop,numPoints)
            builder.appendRoundTube(cylinder,numPoints)

            return builder.build()
        }

我们所做的第一件事情是找出需要多少个顶点表示这个圆柱体,然后用那个数量实例化一个新的 ObjectBuilder。一个圆柱体由一个圆柱体的顶部(就是一个圆)和一个圆柱体的侧面构成,因此所有顶点的数量就等于sizeOfCircleInVertices(numPoints)+sizeOfOpenCylinderInVertices(numPoints)。

然后,我们要计算圆柱体的顶部应该放在哪里,并调用 appendCircle()创建它。通过调用appendRoundTube(),我们也生成了圆柱体的侧面,之后通过返回build()的结果返回生成的数据。这些方法都不存在,我们后续会持续完善他们。

我们为什么要把圆柱体的顶部移动 cylinder.height/2f呢?看一下下图:

这个圆柱体垂直方向以center.y为中心,因此在那放置圆柱体的侧面没有问题。然而,圆柱体顶部需要被放在侧面的顶部,要想这样做,我们需要把它向上移动圆柱体整体高度的一半。

2.2 用三角扇形构建圆形

下一步是写代码用一个三角形扇构造这个圆柱体的顶部。我们会把数据写进vertexData,并使用 offset记录我们写到数组的哪个位置了。

创建一个名为appendCircle()的新方法,并加入如下代码:

    private fun appendCircle(circle:Circle,numPoints:Int){
        val startVertex = offset / FLOAT_PER_VERTEX
        val numVertices = sizeOfInCircleVertices(numPoints)
        vertexData[offset++] = circle.center.x
        vertexData[offset++] = circle.center.y
        vertexData[offset++] = circle.center.z

        for(i in 0..numPoints){
            var angleInRadians = (i.toFloat()/numPoints.toFloat())*(Math.PI.toFloat()*2f)
            vertexData[offset++] = circle.center.x + circle.radius * cos(angleInRadians)
            vertexData[offset++] = circle.center.y
            vertexData[offset++] = circle.center.z + circle.radius * sin(angleInRadians)
        }
    }

要构建三角形扇,我们首先在circle.center定义一个圆心顶点,接着,我们围绕圆心的点按扇形展开,并把第一个点绕圆周重复两次考虑在内。我们接下来使用三角函数和单位圆,的概念生成那些点。

为了生成沿一个圆周边的点,我们首先需要一个循环,它的范围涵盖从0到360度的整个圆,或者0到2π弧度。要找到圆周上的一个点的x的位置,我们要调用cos(angle),要找到它的z的位置,我们调用sin(angle);我们用圆的半径缩放这两个位置。

因为这个圆将被平放在x-z平面上,单位圆的y分量就会映射到y的位置。

2.3 为三角形扇添加一个绘图命令

我们还需要告诉OpenGL如何绘制这个圆柱体的顶部。因为一个圆柱体是由两个基本图元构造的,一个构造顶部的三角形扇,另一个构造侧面的三角形带,所以我们需要一种方法,它可以把这些绘画命令合并在一起,以便在后面只需要调用draw()即可。能实现这些的一个方法是把每一个绘画命令都加入一个绘画命令列表(list)中。

我们要创建一个接口(interface)表示单个绘画命令。在ObjectBuilder顶部加入如下代码:

    interface DrawCommand{
        fun draw()
    }

我们也需要一个实例变量用于保存那些整理好的绘画命令。

在vertexData后面加入如下定义:

    var drawList = ArrayList<DrawCommand>()

现在在appendCircle()尾部加入如下代码:

        drawList.add(object : DrawCommand {
            override fun draw() {
                GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN,startVertex,numVertices)
            }
        })

通过这些代码,我们就创建了一个内部类,它调用glDrawArrays(),并且我们还把这个内部类添加进了绘画命令列表。后面要绘制这个圆柱体时,我们只需要执行列表中的每个draw()方法。

2.4 用三角形带构建圆柱体的侧面

让我们继续新增如下代码:

    private fun appendRoundTube(cylinder:Cylinder,numPoints:Int){
        val startVertex = offset / FLOAT_PER_VERTEX
        val numVertices = sizeOfInOpenCylinderVertices(numPoints)
        val yStart:Float = cylinder.center.y - (cylinder.height/2f)
        val yEnd = cylinder.center.y + (cylinder.height/2f)

正如以前一样,我们要计算出起始顶点和顶点数量,以便于在绘画命令中使用它们。我们也要搞清楚这个圆柱体从哪里开始到哪里结束,这些位置应该如下图所示:

加入如下代码生成实际的三角形带:

        for(i in 0..numPoints){
            var angleInRadians = (i.toFloat()/numPoints.toFloat())*(PI.toFloat()*2f)
            var xPosition = cylinder.center.x + cylinder.radius * cos(angleInRadians)
            var zPosition = cylinder.center.z + cylinder.radius * sin(angleInRadians)

            vertexData[offset++] = xPosition
            vertexData[offset++] = yStart
            vertexData[offset++] = zPosition

            vertexData[offset++] = xPosition
            vertexData[offset++] = yEnd
            vertexData[offset++] = zPosition
        }

我们使用了同前面生成圆周顶点一样的算法,只是这次我们为圆周上的每个点生成了两个顶点,一个是圆柱顶部,另一个是圆柱底部。前面两个点的位置重复两次以使这个圆柱体闭合。

加入绘图命令完成这个方法:

        drawList.add(object:DrawCommand{
            override fun draw() {
                GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP,startVertex,numVertices)
            }
        })

我们使用 GL_TRIANGLE_STRIP告诉 OpenGL绘制一个三角形带。

2.5 返回构建数据

要使 createCylinder()工作,我们只需要定义build()方法。我们要使用它返回GeneratedData对象内部生成的数据。我们还没有定义这个类,因此,在 ObjectBuilder加入如下类:

    class GeneratedData{
        var vertexData:FloatArray
        var drawList:ArrayList<DrawCommand>
        constructor(vertexData:FloatArray,drawList:ArrayList<DrawCommand>){
            this.vertexData = vertexData
            this.drawList = drawList
        }
    }

这只是一个holder类,以便我们可以用单个对象返回顶点数据和绘画命令列表。现在我们只需要定义build():

    private fun build():GeneratedData = GeneratedData(vertexData,drawList)

至此我们构建圆柱体就算完成了。

3. 创建圆柱体管理类

既然我们已经有一个圆柱体的构建器了,那么构建后的数据就需要被统一进行管理,因此在这里我们需要通过一个类来调用构建圆柱体,并对构建后的数据进行管理,为此我们再新建一个Cylinder:

class Cylinder {
    var POSITION_COMPONENT_COUNT = 3

    var radius:Float = 0f
    var height:Float = 0f

    private var vertexArray: VertexArray
    private var drawList:ArrayList<ObjectBuilder.DrawCommand>

    constructor(radius:Float,height:Float,numPointsAroundPuck:Int){
        var generatedData = ObjectBuilder.createCylinder(Cylinder(Point(),radius,height),numPointsAroundPuck)
        this.radius = radius
        this.height = height

        vertexArray = VertexArray(generatedData.vertexData)
        drawList = generatedData.drawList
    }

当一个新圆柱体被创建的时候,它会生成那个物体的数据,用vertexArray把顶点存储在一个本地缓冲区中,并把绘画命令列表存储在drawList中。

让我们完成这个类:

    fun bindData(colorProgram: ColorShaderProgram){
        vertexArray.setVertexAttribPointer(0,colorProgram.aPositionLocation,POSITION_COMPONENT_COUNT,0)
    }

    fun draw(){
        drawList.forEach{
            it.draw()
        }
    }

第一个方法bindData()它把顶点数据绑定到着色器程序定义的属性上。第二个方法onDraw()只是遍历ObjectBuilder.createCylinder()创建的显示列表。

更新着色器

我们还需要更新颜色着色器。我们现在生成的顶点是不包含颜色属性的,因此我们不得不把颜色作为一个uniform传递进去。所有的改动里第一件要做的就是给ShaderProgram加入一个新的常量:

    protected val U_COLOR = "u_Color"

下一步是更新ColorShaderProgram。继续移除所有aColorLocation的引用,包括getColorAttributeLocation(),然后加入如下uniform位置的定义:

    var uColorLocation = 0

更新构造函数设置uniform的位置:

uColorLocation = GLES20.glGetUniformLocation(program,U_COLOR)

要完成这些改动, setUniforms()更新如下:

    fun setUniforms(matrix:FloatArray,r:Float,g:Float,b:Float){
        GLES20.glUniformMatrix4fv(uMatrixLocation,1,false,matrix,0)
        GLES20.glUniform4f(uColorLocation,r,g,b,1f)
    }

我们还需要更新实际的着色器。更新simple_vertex_shader.glsl的内容如下代码所示:

uniform mat4 u_Matrix;

attribute vec4 a_Position;

void main(){
    gl_Position = u_Matrix * a_Position;
    gl_PointSize = 10.0;
}

更新 simple_fragment_shader.glsl如下代码所示:

precision mediump float;

uniform vec4 u_Color;

void main(){
   gl_FragColor = u_Color;
}

集成所有变化

本篇最困难的部分已经完成了。我们学习了如何用简单的几何形状构造圆柱体,并更新了着色器用以反映这些变化。所有剩下的就是把这些变化集成到Renderer;同时,我们还要学习通过加入视图矩阵添加一个相机的概念。

那么我们为什么想添加另一个矩阵呢?当我们最早开始项目的时候,根本没使用任何矩阵。我们首先添加了一个正交矩阵调整宽高比,接着切换到一个透视矩阵获得一个三维投影。之后添加了一个模型矩阵开始来回移动物体。视图矩阵实际只是模型矩阵的扩展;它的使用是出于同样的目的,只不过它平等地应用于场景中的每一个物体。

1.简单的矩阵层次结构

让我们花点儿时间复习一下,把一个物体放到屏幕上的三种主要的矩阵类型。

模型矩阵
模型矩阵是用来把物体放在世界空间(world-space)坐标系的。比如,我们可能有圆柱体的模型,它们初始的中心点都在(0,0,0)。没有模型矩阵,这些模型就会卡在那里:如果我们想要移动它们,就不得不自己更新每个模型的每个顶点。如果不想这样做,我们可以使用一个模型矩阵,把那些顶点与这个矩阵相乘来变换它们。如果我们想把圆柱体移动到(5,5),我们只需要准备一个模型矩阵,它会为我们完成的。

视图矩阵
视图矩阵是出于同模型矩阵一样的原因被使用的,但是它平等地影响场景中的每一个物体。因为它影响所有的东西,它在功能上等同于一个相机:来回移动相机,你会从不同的视角看见那些东西。

使用另外一个矩阵的优势是它让我们预先把许多变换处理成单个矩阵。举个例子,想象一下我们要来回旋转一个场景,并把它移动一定量的距离。能实现这些的一种方式是把同样的旋转和平移调用应用于每一个单个的物体。尽管那样可行,但如果只把这些变换存到另外一个矩阵,并把这个矩阵应用于每个物体,会更容易实现。

投影矩阵
最后,说说投影矩阵。这个矩阵帮助创建三维的幻象,通常只有当屏幕变换方位时,它才会变化。

让我们也复习一下一个顶点如何从它原来的位置变换到屏幕的。

vertexmodel
这是模型坐标系中的一个顶点。例如,被包含在矩形顶点内部的位置。

vertexworld
这是在世界空间中用模型矩阵定位过的一个顶点。

vertexeye
这是与我们的眼睛或相机相对的一个顶点。我们使用一个视图矩阵让所有的顶点在世界空间中绕着我们当前的观察位置移动。

vertexclip
这是被投影矩阵处理过的一个顶点。下一步就是做透视除法,正如我们在前面篇节中解释的一样。

vertexndc

这是归一化设备坐标系中的一个顶点。一旦一个顶点落在这个坐标中,OpenGL就会把它映射到视口,你就能从屏幕上看到它了。

这个链条看上去如下所示:

vertexclip=ProjectionMatrix * vertexeye

vertexclip=ProjectionMatrix * ViewMatrix * vertexworld

vertexclip=ProjectionMatrix * ViewMatrix * ModelMatrix * vertexmodel

要得到正确的结果,我们需要应用本篇中的每个矩阵。

2.更新Render类

让我们继续给Render添加一个视图矩阵,同时,我们还要处理新的圆柱体。我们将首先在类的顶部添加一些新的矩阵定义:

    var viewMatrix = FloatArray(16)
    var viewProjectionMatrix = FloatArray(16)
    var modelViewProjectionMatrix = FloatArray(16)

我们把视图矩阵存储到viewMatrix,其他两个矩阵用于保存矩阵乘积的结果。我们还需要定义一个Cylinder对象添加一个定义:

    lateinit var cylinder: Cylinder
    cylinder = Cylinder(0.08f,0.04f,32)

2.1 初始化矩阵

下一步是更新onSurfaceChanged(),并初始化视图矩阵。更新onSurfaceChanged()匹配如下内容:

    override fun onSurfaceChanged(p0: GL10?, width: Int, height: Int) {
        GLES20.glViewport(0,0,width,height)
        Matrix.perspectiveM(projectionMatrix,0,45f,width.toFloat()/height.toFloat(),1f,10f)
        Matrix.setLookAtM(viewMatrix,0,0f,2.2f,2.2f,0f,0f,0f,0f,1f,0f)
    }

这个方法的第一部分相当标准:设置视口,并建立投影矩阵。下一部分是新内容:调用setLookAtM()创建一个特殊类型的视图矩阵。

setLookAtM(float[] rm, int rmOffset, float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, float upX, float upY, float upZ)

参数描述
float[] rm这是目标数组。这个矩阵的长度应该至少容纳16个元素,以便它能存储视图矩阵
int rmOffsetsetLookAtM()会把结果从rm的这个偏移值开始存进rm
float eyeX, eyeY, eyeZ这是眼睛所在的位置。场景中的所有东西看起来都像是从这个点观察它们一样
float centerX, centerY, centerZ这是眼睛正在看的地方;这个位置出现在整个场景的中心
float upX, upY, upZ要是我们刚才正在讨论你的眼睛,那么这是你的头指向的地方。upY的值为1 意味着你的头笔直指向上方

我们调用setLookAtM()时,把眼睛(eye)设为(0,1.2,2.2),这意味着眼睛的位置在x-z平面上方1.2个单位,并向后2.2个单位。换句话说,场景中的所有东西都出现在你下面1.2个单位和你前面2.2个单位的地方。把中心(center)设为(0,0,0),意味着你将向下看你前面的原点,并把指向(up)设为(0,1,0),意味着你的头是笔直指向上面的,这个场景不会旋转到任何一边。

2.2更新DrawFrame

在运行程序之前,只剩下最后几个改动了,看看这些新的改动。在 glClear()调用后面把下面的代码加人 onDrawFrame():

 Matrix.multiplyMM(viewProjectionMatrix,0,projectionMatrix,0,viewMatrix,0)

这会把投影和视图矩阵乘在一起的结果缓存到viewProjectionMatrix。用如下代码替换onDrawFrame()的余下部分:

        positionTableInScene()
        textureProgram.useProgram()
        textureProgram.setUniforms(modelViewProjectionMatrix,texture)
        table.bindData(textureProgram)
        table.draw()

        colorProgram.useProgram()
        positionObjectInScene(0f,cylinder.height/2f,0f)
        colorProgram.setUniforms(modelViewProjectionMatrix,0.8f,0.8f,1f)
        cylinder.bindData(colorProgram)
        cylinder.draw()

这段代码的大部分与上一篇相同,但是有一些关键的区别。第一个不同之处是我们在绘制那些物体之前调用了positionTableInScene()和positionObjectInScene()。我们在绘制圆柱体之前还更新了setUniforms(),并且添加了绘制圆柱体的代码。

让我们添加positionTableInScene()的定义:

    private fun positionTableInScene(){
        Matrix.setIdentityM(modelMatrix,0)
        Matrix.rotateM(modelMatrix,0,-90f,1f,0f,0f)
        Matrix.multiplyMM(modelViewProjectionMatrix,0,viewProjectionMatrix,0,modelMatrix,0)
    }

这个桌子(之前的矩形)原来是以x和y坐标定义的,因此要使它平放在地上,我们需要让它绕x轴向后旋转90度。注意,不像前面的篇节,我们不需要把桌子平移一定的距离,因为我们想要桌子在世界坐标里保持在位置(0,0,0),并且视图矩阵已经想办法使桌子对我们可见了。

最后一步是通过把viewProjectionMatrix和modelMatrix相乘将所有矩阵合并在一起,并把结果存储到modelViewProjectionMatrix,它接着会被传递给着色器程序。

让我们把positionObjectInScene()的定义也添加进来:

    private fun positionObjectInScene(x:Float,y:Float,z:Float){
        Matrix.setIdentityM(modelMatrix,0)
        Matrix.translateM(modelMatrix,0,x,y,z)
        Matrix.multiplyMM(modelViewProjectionMatrix,0,viewProjectionMatrix,0,modelMatrix,0)
    }

圆柱体已经被定义好,并被水平放在x-z平面上了,因此不需要旋转它们。我们要根据传递进来的参数平移它们,将它们放在桌子上方正确的位置。运行这个程序。如果一切按计划顺利进行,我们就会看到我们所设计的效果,这里运行截图就略了…,圆柱体看起来应该是放在桌子中间的。

小结

在本章节中,我们成功地掌握了如何生成三角形带和三角形扇,并将它们组合成具体的圆柱体。我们不仅学习了如何构建这些几何形状,还学会了如何将绘制命令封装起来,使得我们可以方便地将它们绑定到一个单一的命令中,简化了操作过程。

此外,我们还引入了矩阵层次结构的概念,特别是用于相机的视图矩阵和用于在世界空间中定位物体的模型矩阵。这种层次结构的引入,极大地方便了我们对场景的操作和物体的移动,使得整个渲染过程变得更加直观和高效。通过这些学习,我们能够更好地控制和渲染复杂的三维场景。

OpenGL使用入门笔记(一) OpenGL使用入门笔记(一)1 几个基本函数和概念了解1.1一个入门的OpenGL程序1.2一个进阶的绘图程序解释一下函数什么意思1.3 GLUT函数及解释1.4 走样与反走样啥意思2 绘制几何模型2.1 画点2.2 画线2.2.1 线段2.2.2 线段序列2.2.3 封闭线段2.3 画三角形2.3.1 三角形2.3.2 三角形序列2.4 画四边形2.4.1 四边形2.4.1 四边形条带2.5 ... 阅读详情

相关推荐

Android音视频开发-如何用OPENGL ES绘制圆柱体

设置圆柱体的控制属性,主要包括纹理、高度、截面半径、截面角度切分单位和高度切分单位,这些属性用于控制圆柱体的大小 *定义各个圆柱体绘制类的三角形绘制方法和工具方法 *实现圆柱体的线性会执法,线性会执法和三角形会执法顶点的获取方法相同,只是采用的绘制顶点顺序和渲染方法不同,并且先行绘制没有光照和纹理贴图。*指定屏幕所要显示的假面,并对见、界面进行相关设置 *为Activity设置恢复处理,当Acitvity恢复设置时显示界面同样应该恢复 *当Activity暂停设置时,显示界面同样应该暂停。

m0_70749039的博客 281

OpenGLES:绘制一个混色旋转的3D圆柱

圆柱绘制的关键点就是将圆柱拆解成:两个Z坐标不为0的圆 + 一个长方形的圆柱面

肖云鹤的博客 2295

Android 游戏开发从入门到精通【2.6】

本文介绍了在Android中使用OpenGL ES实现3D图形渲染的技术要点。主要内容包括: 纹理映射技术:通过纹理对象管理技术优化纹理加载性能,设置纹理重复模式提高渲染效率。详细说明了纹理坐标设置方法和三种纹理拉伸方案的选择。 三角形纹理贴图实现:通过Dad.java和yisuo.java两个文件演示了三角形纹理贴图的完整实现流程,包括纹理初始化、顶点坐标设置、触控交互处理等。 圆柱体3D建模:通过zhuCH类实现圆柱体的几何建模,包含顶点坐标计算、法向量生成、纹理坐标自动切分等功能。配合光照和材质设置,

qq_43416206的博客 1309

OpenGL绘制圆柱体

圆柱体是3D图形中的基本图形之一。下面是一个使用OpenGL绘制圆柱体的示例代码。 #include <GL/glut.h> #include <math.h> void drawCylinder(float radius, float height, int slices) { GLUquadric *quadric = gluNewQuadric(); ...

weixin_42593130的博客 1074

opengl绘制移动的安卓机器人

图形学实验要完成opengl绘制移动的安卓机器人。 在参考同学的作品后终于自己完成了一份。一下贴一下我查到的opengl中我用到的函数以及作用。附件里贴了 我的工程以及opengl的库,以及相关文档,有帮助的话可以下载哦。。  (那些文件我会上传分享的,需要的话到我的资源啥的找一找) csdn这点很差,博客都不能带文件的。。哎)CClientDC *m_pDC;//用户设备指针 BOOL My

G.D.Retop的专栏 2262

手把手教你用Matlab解析Zygo .dat文件:从二进制数据到面形图(附完整流程)

本文详细介绍了如何使用Matlab解析Zygo干涉仪生成的.dat文件,从二进制数据读取到面形图可视化的完整流程。通过实例代码演示了文件结构解析、数据矩阵读取、高阶Zernike拟合等关键技术,帮助工程师和研究人员高效处理光学检测数据,提升分析精度和工作效率。

weixin_30731305的博客 451

openGL创建平面、立方体、圆柱体、斜面和棱柱Demo

OpenGL 创建平面、立方体、圆柱体、斜面和棱柱 Demo

zq9955的博客 858

PCL读取点云数据报错:Failed to find match for field ‘x‘/ ‘y‘/ ‘z‘.

原因:使用matlab生成的pcd文件,点云数据是双精度类型,pcl处理的点云数据是单精度类型。解决方案:使用matlab生成的点云坐标单精度化,然后在生成点云加以存储。

little_white138的博客 870

android 使用OPENGL ES绘制一个圆柱体-三维空间

编写jiem.java     *指定屏幕所要显示的假面,并对见、界面进行相关设置     *为Activity设置恢复处理,当Acitvity恢复设置时显示界面同样应该恢复     *当Activity暂停设置时,显示界面同样应该暂停    package com.scout.eeeeeee; import android.app.Activity; import andro

刘国栋的博客 6095

Unity开源游戏源码精选:17个亲测可运行教学项目

Unity游戏开发中,掌握真实项目结构与工程实践远比熟记API更重要。本文围绕‘Unity开源项目’和‘游戏源码’两大核心概念,解析高质量开源资源的本质特征——结构清晰、逻辑可溯、修改即时反馈。从基础的MonoBehaviour生命周期组织,到进阶的ScriptableObject数据驱动、事件总线解耦,再到架构级的DOTS ECS设计与网络同步模式,技术价值在于将抽象原理落地为可调试、可验证、可迁移的代码范式。适用于新手建立开发直觉、中级开发者突破系统设计瓶颈,以及团队构建可维护框架。所有项目均基于Uni

weixin_31528001的博客 720

超级详细!!!新手使用AutoDL跑深度学习(MixVPR项目)的学习记录

新手第一次在AutoDL上跑深度学习的实验,遇到一些问题,简单记录一下。如果大家发现问题,欢迎和我讨论。

weixin_45152283的博客 5万+

android opengl es bitmap,android使用OPENGL ES绘制圆柱体

本文实例为大家分享了android使用OPENGL ES绘制圆柱体的具体代码,供大家参考,具体内容如下效果图:编写jiem.java*指定屏幕所要显示的假面,并对见、界面进行相关设置*为Activity设置恢复处理,当Acitvity恢复设置时显示界面同样应该恢复*当Activity暂停设置时,显示界面同样应该暂停package com.scout.eeeeeee;import android.a...

weixin_40009099的博客 363

人像抠图 + OpenGL ES 还能这样玩?没想到吧(附带源码)

OpenGL ES 利用抠图算法实现人像留色。

字节流动 3万+

Android OpenGL ES2.0从放弃到入门(四)—— 绘制圆柱体和球体

上一篇文章,我们绘制了部分几何图形和立方体,今天我们来绘制点带弧面的立体图形 圆柱体 圆柱体咋一看有点费劲,我们不妨拆解来看。一个圆柱体可以分为两个圆形底面和一个弧面的侧面。在前面我们已经画过圆形了,我们这里就考虑弧面去如何实现就行了。类比圆形的实现原理,我们也可以把侧面圆筒一样的弧面,分解成棱柱,分解的越细、棱柱的面越多看起来就和圆筒是一样的了。而每个棱柱的面都是个矩形,我们再把矩形拆解成两个三...

weixin_39560693的博客 1548

【虚幻引擎UE】UE5 阴影异常与优化

【虚幻引擎UE】UE5 阴影异常与优化解决方案合集

灵境引路人 3万+

Debezium报错处理系列之二十一:Unable to connect: Communications link failure.The driver has not received any

Debezium报错系列之二十一:Unable to connect: Communications link failure.The driver has not received any。

zhengzaifeidelushang的博客 547

freeglut中提供的几种立体几何对象绘制的android移植

在freeglut_geometry.c中定义了几种常见图形的绘制,包括立方体, 球面, 圆锥体, 圆柱体, 圆环面, 十二面体, 八面体, 四面体, 二十面体, 菱形十二面体(斜十二面体)等几种立体几何对象的绘制。 移植后的Android Java代码如下所示: /** * fy...

chijiandao3197的博客 441

哨兵1号后向散射系数土壤水分反演

打开之前预处理之后的VH和VV极化的后向散射系数转存的tiff文件。

所有专家都曾始于新手 3293

OpenGL: 圆柱面绘制贴图

最近看到dx中的sample中有用顶点数组实现的圆柱体贴图的程序。于是自己写了一个圆柱体的类,支持生成纹理坐标,法线,坐标。构造圆柱体的参数主要有:水平分段数(就是上下圆的分段数目),高度上的分段数目,(至少有两段:上下两个圆面)半径,高度。顶点生成的原理:将圆柱面展开就是一个矩形。而纹理也是一个矩形区域,需要注意的是圆柱面是一个首尾相接的封闭面。效果图如下:代码:头文件#ifndef CYLIN

Augusdi的专栏 1万+

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

OpenGL.ES在Android上的简单实践:7-曲棍球(构建冰球木槌 上)

OpenGL.ES在Android上的简单实践:7-曲棍球(构建冰球木槌 上)     这一章的内容大体上,不涉及OpenGL.ES的内容,而且在实际商业开发中是一般不会使用到的。然而,我想说的是,作为一个专业的三维开发工程师,图形开发者,GPU研究人员(等等更多高逼格称呼的程序员)来说,掌握基础的图形构建是很有必要的。想想大千世界的物体不都是由这些简单几何体一步步构建起来的吗? 可能又有...

vx number:zzr_admin,拉入技术交流群。 882

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

OpenGL.ES在Android上的简单实践:8-曲棍球(构建冰球木槌 下 & 模型视图投影矩阵)

OpenGL.ES在Android上的简单实践:8-曲棍球(构建圆柱体木槌) 1、创建木槌顶点数据 本篇文章继续第7篇文章之后的内容,我们通过ObjectBuilder.cratePuck创建冰球了(偏平圆柱体),这次我们在之前的基础上创建简易木槌(矮扁的圆柱底部+苗条的圆柱摇杆)如下图 手柄的高度占整体高度的75%,而基部的高度占整体高度25%。我们也可以看出手柄的宽度占整体宽...

vx number:zzr_admin,拉入技术交流群。 1007

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

3D画一个圆

大家要知道GLSurfaceview里面没有像2D里面的drawCircle(x,y,r,paint);的函数,所以得想到其他的方法了。 画圆就是根据R*R=R*RCOS^2+R*RSIN^2;循环N次,这样就可以得到N个点,点点之间都是直线,所以点越多,圆越圆。下面让我们看看代码吧。     package wyf.lgz; import android.app.Activity;

nate的专栏 2826

Android OpenGL ES(九)----构建几何物体

1.三角形扇 一个三角形扇以一个中心顶点作为起始,使用相邻的两个顶点创建第一个三角形,接下来的每个顶点都会创建一个三角形,围绕起始的中心点按扇形展开。为了使这个扇形闭合,我们只需要在最后重复第二个点。(以长方形为例) 构建三角形扇的步骤,如下图所示: 要使用OpenGL绘制这个三角形扇,需要在渲染类的onDrawFrame()中,使用如下方法: GLES20.g

李元静 4582

Android OpenGL触控反馈

在许多游戏和应用程序中,优秀的用户交互设计是至关重要的,它能够让用户感受到与现实世界中物体的互动,即便他们实际上只是在与屏幕上的像素进行交互。而在安卓上则主要以触控交互为主。本篇我们将探讨如何通过添加触控功能来增强一个场景的交互性。我们将学习三维空间中的碰撞检测和交互技术,使得我们能够在屏幕上拖动圆柱体的。

ZLGSPACE的博客 1441

Android应用:3D圆柱体

总共三个类 package wyf.lgz; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.util.ArrayList; import javax.microedition.khronos.opengles.GL10; public cla

棺材深處 3815

Android OpenGL 构建几何体(六)

1.三角形扇 一个三角形扇以一个中心顶点作为起始,使用相邻的两个顶点创建第一个三角形,接下来的每个顶点都会创建一个三角形,围绕起始的中心点按扇形展开。为了使这个扇形闭合,我们只需要在最后重复第二个点。(以长方形为例) 构建三角形扇的步骤,如下图所示: 要使用OpenGL绘制这个三角形扇,需要在渲染类的onDrawFrame()中,使用如下方法:

赵德波(zhaodebbs)的专栏 843
上一篇: Android OpenGL 进入三维
下一篇: Android OpenGL触控反馈
大耳猫
博客等级 码龄4年 483粉丝 75原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值