OpenGL ES 简单教程

什么是OpenGL ES?

OpenGL ES (为OpenGL for Embedded System的缩写) 为适用于嵌入式系统的一个免费二维和三维图形库。

为桌面版本号OpenGL 的一个子集。

OpenGL ES 定义了一个在移动平台上可以支持OpenGL最基本功能的精简标准。以适应如手机。PDA或其他消费者移动终端的显示系统。

Khronos Group 定义和管理了OpenGL ES标准。

OpenGL 与 OpenGL ES的关系OpenGL ES 是基于桌面版本号OpenGL 的:

  • OpenGL ES 1.0  基于OpenGL 1.3 , 在2003年公布
  • OpenGL ES 1.1   基于OpenGL 1.5 ,  在2004年公布
  • OpenGL ES 2.0  基于OpenGL2.0,   在2007年公布
  • OpenGL 2.0 向下兼容OpenGL 1.5   而 OpenGL ES 2.0 和OpenGL ES 1.x 不兼容,是两种全然不同的实现。

OpenGL ES管道(Pipeline)OpenGL ES 1.x 的工序是固定的。称为Fix-Function Pipeline,能够想象一个带有非常多控制开关的机器。虽然加工的工序是固定的。可是能够通过打开或关闭开关来设置參数或者打开关闭某些功能。 OpenGL
ES 2.0 同意提供编程来控制一些重要的工序。一些“繁琐”的工序比方栅格化等仍然是固定的。(这些开关被就是state,注意应该尽量少的改变state。以免影 响性能)

  • 管道“工序”大致能够分为 Transformation Stage 和 Rasterization Stage两大步。
  • OpenGL ES 支持的基本图形为 点Point, 线Line, 和三角形Triangle 。其他全部复制图形都是通过这几种基本几何图形组合而成。
  • 在发出画图指令后,会对顶点(Vertices)数组进行指定的坐标变换或光照处理。
  • 顶点处理完毕后,通过Rasterizer 来生成像素信息,称为”Fragments“ 。
  • 对于Fragment 在经过Texture Processing, Color Sum ,Fog 等处理并将终于处理结果存放在内存中(称为FrameBuffer)。
  • OpenGL 2.0能够通过编程来改动上述红色的部分的步骤,称为Programmable Shader.

OpenGL ES API 命名习惯

  • 定义的常量都以GL_为前缀。比方GL10.GL_COLOR_BUFFER_BIT
  • OpenGL ES 指令以gl开头 ,比方gl.glClearColor
  • 某些OpenGL指令以3f 或4f结尾,3和4代表參数的个数,f代表參数类型为浮点数,如gl.glColor4f 。i,x 代表 int如 gl.glColor4x
  • 相应以v结尾的OpenGL ES 指令。代表參数类型为一个矢量(Vector) ,如 glTexEnvfv
  • 全部8-bit整数相应到byte 类型,16-bit 相应到short类型,32-bit整数(包含GLFixed)相应到int类型,而全部32-bit 浮点数相应到float 类型。
  • GL_TRUE,GL_FALSE 相应到boolean类型
  • C字符串((char*)) 相应到Java 的 UTF-8 字符串。

创建简单的opengl es实例http://developer.Android.com/resources/tutorials/opengl/opengl-es10.html基本几何图形定义OpenGL
ES 支持绘制的基本几何图形分为三类:点。线段。三角形。

也就是说OpenGL ES 仅仅能绘制这三种基本几何图形。不论什么复杂的2D或是3D图形都是通过这三种几何图形构造而成的。 OpenGL ES提供了两类方法来绘制一个空间几何图形:

  • public abstract void glDrawArrays(int mode,
    int first, int count) 使用VetexBuffer 来绘制,顶点的顺序由vertexBuffer中的顺序指定。
  • public abstract void glDrawElements(int
    mode, int count, int type, Buffer indices) ,能够又一次定义顶点的顺序,顶点的顺序由indices Buffer 指定。

mode列表:GL_POINTS 绘制独立的点、GL_LINE_STRIP绘制一条线段、GL_LINE_LOOP绘制一条封闭线段(首位相连)、GL_LINES绘制多条线段、GL_TRIANGLES绘制多个三角形(两两不相邻)、GL_TRIANGLE_STRIP绘制多个三角形(两两相邻)、GL_TRIANGLE_FAN以一个点为顶点绘制多个相邻的三角形 相应顶点除了能够为其定义坐标外,还能够指定颜色,材质,法线(用于光照处理)等。

glEnableClientState 和 glDisableClientState 能够控制的pipeline开关能够有:GL_COLOR_ARRAY (颜色) ,GL_NORMAL_ARRAY (法线), GL_TEXTURE_COORD_ARRAY (材质), GL_VERTEX_ARRAY(顶点), GL_POINT_SIZE_ARRAY_OES等。 相应的传入颜色,顶点,材质,法线的方法例如以下: glColorPointer(int size,int type,int stride,Buffer pointer)
glVertexPointer(int size, int type, int stride, Buffer pointer) glTexCoordPointer(int size, int type, int stride, Buffer pointer) glNormalPointer(int type, int stride, Buffer pointer) OpenGL ES 内部存放图形数据的Buffer有COLOR ,DEPTH (深度信息)等,在绘制图形仅仅前一般须要清空COLOR 和 DEPTH
Buffer。三维坐标系及坐标变换初步OpenGL ES图形库终于的结果是在二维平面上显示3D物体,这个过程能够分成三个部分:

  • 坐标变换。坐标变换通过使用变换矩阵来描写叙述,因此学习3D画图须要了解一些空间几何,矩阵运算的知识。三维坐标通常使用齐次坐标来定义。变换矩阵操作能够分为视角(Viewing),模型(Modeling)和投影(Projection)操作,这些操作能够有选择。平移。缩放,正側投影。透视投影等。
  • 因为终于的3D模型须要在一个矩形窗体中显示,因此在这个窗体之外的部分须要裁剪掉以提高画图效率,相应3D图形,裁剪是将处在剪切面之外的部分扔掉。
  • 在终于绘制到显示器(2D屏幕),须要建立起变换后的坐标和屏幕像素之间的相应关系,这通常称为“视窗”坐标变换(Viewport) transformation.

假设我们使用照相机拍照的过程做类比。能够更好的理解3D 坐标变换的过程。

  • 拍照时第一步是架起三角架并把相机的镜头指向须要拍摄的场景,相应到3D 变换为viewing transformation (平移或是选择Camera )
  • 然后摄影师可能须要调整被拍场景中某个物体的角度,位置。比方摄影师给架好三角架后给你拍照时,能够要让你调整站立姿势或是位置。相应到3D绘制就是Modeling transformation (调整所绘模型的位置,角度或是缩放比例)。
  • 之后摄影师能够须要调整镜头取景(拉近或是拍摄远景),相机取景框所能拍摄的场景会随镜头的伸缩而变换。相应到3D画图则为Projection transformation(裁剪投影场景)。
  • 按 下快门后,对于数码相机能够直接在屏幕上显示当前拍摄的照片。一般能够充满整个屏幕(相当于将坐标做规范化处理NDC)。此时你能够使用缩放放 大功能显示照片的部分。相应到3D画图相当于viewport transformation (能够对终于的图像缩放显示等)

对于Viewing transformation (平移,选择相机)和Modeling transformation(平移,选择模型)能够合并起来看,仅仅是应为向左移动相机,和相机不同将模型右移的效果是等效的。 在OpenGL ES 中。

  • 使用GL10.GL_MODELVIEW 来同一时候指定viewing matrix 和modeling matrix.
  • 使用GL10.GL_PROJECTION 指定投影变换。OpenGL 支持透视投影(3D)和正側投影(2D)。
  • 使用glViewport 指定 Viewport 变换。

通用的矩阵变换指令 这里介绍相应指定的坐标系(比方viewmodel, projection或是viewport) Android OpenGL ES支持的一些矩阵运算及操作。

矩阵本身能够支持加减乘除,对角线全为1的4X4
矩阵成为单位矩阵Identity Matrix 。

  • 将当前矩阵设为单位矩阵的指令 为glLoadIdentity().
  • 矩阵相乘的指令glMultMatrix*() 同意指定随意矩阵和当前矩阵相乘。
  • 选择当前矩阵种类glMatrixMode(). OpenGL ES 能够执行指定GL_PROJECTION,GL_MODELVIEW等坐标系,兴许的矩阵操作将针对选定的坐标。
  • 将当前矩阵设置成随意指定矩阵glLoadMatrix*()
  • 在栈中保存当前矩阵和从栈中恢复所存矩阵,能够使用glPushMatrix()glPopMatrix()
  • 特定的矩阵变换平移glTranslatef(),旋转glRotatef() 和缩放glScalef()

方法public abstract void glTranslatef (float
x, float y, float z) 用于坐标平移变换。 方法public abstract void glRotatef(float
angle, float x, float y, float z)用来实现选择坐标变换,单位为角度。

(x,y,z)定义旋转的參照矢量方向。

多次旋转的顺序很重要。

方法public abstract void glScalef (float
x, float y, float z)用于缩放变换。

在进行平移。旋转,缩放变换时,全部的变换都是针对当前的矩阵(与当前矩阵相乘)。假设须要将当前矩阵回复最初的无变换的矩阵,能够使用单位矩阵(无平移,缩放。旋转)。 public abstract void glLoadIdentity()。 在栈中保存当前矩阵和从栈中恢复所存矩阵,能够使用public
abstract void glPushMatrix() 和public abstract void glPopMatrix()。 在进行坐标变换的一个好习惯是在变换前使用glPushMatrix保存当前矩阵,完毕坐标变换操作后,再调用glPopMatrix恢复原先的矩阵设置。Viewing和Modeling(MODELVIEW)
变换 ndroid OpenGL ES 的GLU包有一个辅助函数gluLookAt提供一个更直观的方法来设置modelview 变换矩阵: void gluLookAt(GL10 gl, float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, float upX, float upY, float upZ)

  • eyex,eyey,eyez 指定观測点的空间坐标。
  • tarx,tary,tarz ,指定被观測物体的參考点的坐标。
  • upx,upy,upz 指定观測点方向为“上”的向量。(0,1,0)

投影变换Projection

投影变换则相应于调整相机镜头远近来取景。 以下代码设置当前Matrix模式为Projection投影矩阵: gl.glMatrixMode(GL_PROJECTION); gl.glLoadIdentity(); OpenGL ES能够使用两种不同的投影变换:透视投影(Perspective Projection)和正側投影(Orthographic Projection)。Android
OpenGL ES提供了一个辅助方法gluPerspective()能够更简单的来定义一个透视投影变换: GLU.gluPerspective(GL10 gl, float fovy, float aspect, float zNear, float zFar)

  • fovy: 定义视锥的view angle.
  • aspect:  定义视锥的宽高比。
  • zNear: 定义裁剪面的近距离。就是前面距离原点的距离
  • zFar: 定义创建面的远距离。

    就是后面距离原点的距离

正側投影(Orthographic Projection) 正側投影,它的视锥为一长方体,特点是物体的大小不随到观測点的距离而变化,投影后能够保持物体之间的距离和夹角。

定义3D模型的前面和后面以下代码设置逆时针方法为面的“前面”:gl.glFrontFace(GL10.GL_CCW);
打开 忽略“后面”设置:gl.glEnable(GL10.GL_CULL_FACE);然后明白指明“忽略“哪个面的代码例如以下: gl.glCullFace(GL10.GL_BACK); FrameBuffer、Depth BufferOpenGL ES 中的FrameBuffer 指的是存储像素的内存空间。相应一个二维图像。假设屏幕分辨率为1280X1024 ,假设屏幕支持24位真彩色 (RGB),则存储这个屏幕区域的内存至少须要1024X1280X3个字节。此外假设须要支持透明度(Alpha),则一个像素须要4个字节。
在终于OpenGL ES写入这些Buffer时,OpenGL ES提供一些Mask 函数能够控制Color Buffer 中RGBA通道,是否同意写入Depth Buffer 等,这些Mask 函数能够打开或是关闭某个通道,仅仅有通道打开后,相应的分量才会写入指定Buffer,比方你能够关闭红色通道。这样最后写道Color Buffer中就不含有红色。

OpenGL ES 中Depth Buffer 保存了像素与观測点之间的距离信息,在绘制3D图形时,将仅仅绘制可见的面而不去绘制隐藏的面。这个过程叫”Hidden
surface removal” ,採用的算法为”The depth buffer algorithm”。The depth buffer algorithm 在OpenGL ES 3D绘制的过程中这个算法是自己主动被採用的,可是了解这个算法有助于理解OpenGL ES 部分API的使用。 以下给出了OpenGL ES中与Depth Buffer相关的几个方法:

  • gl.Clear(GL10.GL_DEPTH_BUFFER_BIT) 清空Depth Buffer (赋值为1.0)通常清空Depth Buffer和Color Buffer同一时候进行。
  • gl.glClearDepthf(float depth) 指定清空Depth Buffer是使用的值。缺省为1.0,通常无需改变这个值,
  • gl.glEnable(GL10.GL_DEPTH_TEST) 打开depth Test
  • gl.glDisable(GL10.GL_DEPTH_TEST) 关闭depth Test

OpenGL光照模型 为了能看出3D效果。给场景中加入光源。

假设没有光照,绘出的球看上去和一个二维平面上圆没什么区别 OpenGL 光照模型中终于的光照效果能够分为四个组成部分:Emitted(光源), ambient(环境光),diffuse(漫射光)和specular(镜面反射光),终于结果由这四种光叠加而成。

  • Emitted : 一般仅仅发光物体或者光源,这样的光不受其他光源的影响。 ambient: 环境光假设射到某个平面,其反射方向为全部方向。Ambient 没有位置方向,仅仅有颜色。diffuse:当一束平行的入射光线射到粗糙的表面时,因面上凹凸不平,所以入射线尽管互相平行,因为各点的法线方向不一致,造成反射光线向不同 的方向无规则地反射,这样的反射称之为“漫反射”或“漫射”。这个反射的光则称为漫射光。漫射光射到某个平面时,其反射方向也为全部方向。diffuse
    仅仅依赖于光源的方向和法线的方向。


    specular : 一般指物体被光源直射的高亮区域,也能够成为镜面反射区。如金属。specular依赖于光源的方向,法线的方向和视角的方向。 设置光照效果Set LightingOpenGL ES API怎样使用光照效果:

    • 设置光源
    • 定义法线
    • 设置物体材料光学属性

    光源 OpenGL ES中能够最多同一时候使用八个光源。分别使用0到7表示。

    OpenGL ES光源能够分为

    • 平行光源(Parallel light source), 代表由位于无限远处均匀发光体,太阳能够近似控制平行光源。
    • 点光源(Spot light source)  如灯泡就是一个点光源。发出的光能够指向360度,能够为点光源设置光衰减属性(attenuation)或者让点光源仅仅能射向某个方向(如射灯)。

    以下方法能够打开某个光源,使用光源首先要开光源的总开关:gl.glEnable(GL10.GL_LIGHTING); 然后能够再打开某个光源如0号光源:gl.glEnable(GL10.GL_LIGHT0); 设置光源方法例如以下:

    • public void glLightfv(int light,int pname, FloatBuffer params)
    • public void glLightfv(int light,int pname,float[] params,int offset)
    • public void glLightf(int light,int pname,float param)
    • light 指光源的序号,OpenGL ES能够设置从0到7共八个光源。
    • pname: 光源參数名称。能够有例如以下:GL_SPOT_EXPONENT, GL_SPOT_CUTOFF, GL_CONSTANT_ATTENUATION,  GL_LINEAR_ATTENUATION, GL_QUADRATIC_ATTENUATION, GL_AMBIENT, GL_DIFFUSE,GL_SPECULAR, GL_SPOT_DIRECTION, GL_POSITION
    • params 參数的值(数组或是Buffer类型)。

    当中为光源设置颜色的參数类型为上述蓝色值。能够分别指定R,G,B,A 的值。指定光源的位置的參数为GL_POSITION,值为(x,y,z,w):平行光将w 设为0.0,(x,y,z)为平行光的方向。 法线 在场景中设置好光源后,下一步要为所绘制的图形设置法线(Normal),仅仅有设置了法线,光源才干在所会物体上出现光照效果。

    三维平面的法线是垂直于该平面的三维向量。曲面在某点P处的法线为垂直于该点切平面的向量
    打开法线数组gl.glEnableClientState(GL10.GL_NORMAL_ARRAY); 和设置颜色类似,有两个方法能够为平面设置法线,一是public void glNormal3f(float nx,float ny,float nz) 这种方法为兴许全部平面设置相同的方向,直到又一次设置新的法线为止。 为某个顶点设置法线:public void glNormalPointer(int type,int stride, Buffer pointer)

    • type  为Buffer 的类型,能够为GL_BYTE, GL_SHORT, GL_FIXED,或 GL_FLOAT
    • stride: 每一个參数之间的间隔。
    • pointer: 法线值。

    规范化法向量,比方使用坐标变换(缩放),假设三个方向缩放比例不同的话。顶点或是平面的法线可能就有变化。此时须要打开规范化法线设置:gl.glEnable(GL10.GL_NORMALIZE); 经过规范化后法向量为单位向量(长度为1)。

    同一时候能够打开缩放法线设置gl.glEnable(GL10.GL_RESCALE_NORMAL); 设置物体材料光学属性设置物体表面材料(Material)的反光属性(颜色和材质)的方法例如以下:
            gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, ambient, 0);         gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, diffuse, 0);         gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, specular, 0);           gl.glEnable(GL10.GL_COLOR_MATERIAL);  
    此外,方法glLightModleXX给出了光照模型的參数 public void glLightModelf(int pname,float param) public void glLightModelfv(int pname,float[] params,int offset) public void glLightModelfv(int pname,FloatBuffer params)

    • pname: 參数类型,能够为GL_LIGHT_MODEL_AMBIENT和GL_LIGHT_MODEL_TWO_SIDE
    • params: 參数的值。

    终于顶点的颜色由这些參数(光源,材质光学属性,光照模型)综合决定(光照方程计算出)。

而另外和传统 OpenGL 在开发上差异较大的部分,包含了:

在 OpenGL ES 2.0 裡也没有 OpenGL 的 matrix stack,程式开发人员必需要自行计算投影矩阵以及各物件的 transform matrix,再传到 shader 裡做计算;尽管在 GPU 端的程式是有矩阵计算的功能。可是在 CPU 端就需要自己实作矩阵计算的基本演算法了。

在 OpenGL ES 裡没有 glBegin() / glEnd() 这样的 immediate mode 的函式,vertex 资料必须用 buffer object 或 vertex array 来处理。

取消了 GL_QUADS / GL_POLYGONS 这两种 primitive 类型。

vertex 的全部资讯(包括本身的位置、颜色、normal 等等)都变成以抽象的 vertex attrib 来处理,须要自行定义并在 vertex shader 裡计算。

Lighting、material 也都是以抽象的形式,以 uniform 变数形式传进 shader 并自行计算。

这些仅仅是一些比較大的差异,其它另一些地方也都不一样,就不在这边列举了。而实际上,以这些部份来看,OpenGL ES 2.0 在概念上与其说是接近 OpenGL 2.0,事实上更接近 OpenGL 3.x 的 Core profile 了~所以基本上以 OpenGL 3.0 的概念来写 OpenGL ES 2.0 的程式,应该会更为适合;仅仅是 OpenGL ES 2.0 的功能又比 OpenGL 3.x 少了些就是了。

时间: 04-19

OpenGL ES 简单教程的相关文章

iOS开发-OpenGL ES入门教程1

http://www.jianshu.com/p/750fde1d8b6a 这里是一篇新手教程,环境是Xcode7+OpenGL ES 2.0,目标写一个OpenGL ES的hello world.OpenGL ES系列教程在这里.OpenGL ES系列教程的代码地址 你的star和fork是我的源动力,你的意见能让我走得更远. 核心思路 通过GLKit,尽量简单地实现把一张图片绘制到屏幕. 效果展示 具体细节 1.新建OpenGL ES上下文 - (void)setupConfig { //新

Android OpenGL ES 开发教程 从入门到精通

From:http://blog.csdn.net/mapdigit/article/details/7526556 Android OpenGL ES 简明开发教程 Android OpenGL ES 简明开发教程一:概述 Android OpenGL ES 简明开发教程二:构造OpenGL ES View Android OpenGL ES 简明开发教程三:3D绘图基本概念 Android OpenGL ES 简明开发教程四:3D 坐标变换 Android OpenGL ES 简明开发教程五

[转]Android OpenGL ES 开发教程 从入门到精通

本文转自:http://blog.csdn.net/mapdigit/article/details/7526556 Android OpenGL ES 简明开发教程 Android OpenGL ES 简明开发教程一:概述 Android OpenGL ES 简明开发教程二:构造OpenGL ES View Android OpenGL ES 简明开发教程三:3D绘图基本概念 Android OpenGL ES 简明开发教程四:3D 坐标变换 Android OpenGL ES 简明开发教程五

Android OpenGL ES绘图教程之二 : 定义形状

在OpenGL ES view中可以定义要绘制图形的形状,是你创建高端图形杰作的第一步.在不知道一些基础的情况下来绘制会有点棘手,比如OpenGL ES是如何定义图形对象的. 本教程解释了OpenGL ES坐标系统与Android设备屏幕的关系,如果定义基础的形状,比如三角形和四边形. 1. 定义一个三角形 OpenGL ES允许你在三维坐标系统中定义绘制对象,所以在绘制三角形之前,必须定义它的坐标.在OpenGL里面,定义坐标的典型方式是定义一个浮点型的顶点坐标数组,为了最大化效率,将这些坐标

Android OpenGL ES绘图教程之一 : 构建OpenGL ES 环境

为了在Android应用中使用OpenGL ES绘图,首先必须要创建一个view容器.一个最简单的方法是实现GLSurfaceView和GLSurfaceView.Renderer.GLSurfaceView一个view容器,用来显示OpenGL绘制的图形,GLSurfaceView.Renderer用来控制GLSurfaceView里面图形的绘制.更多的信息,请参考 OpenGL ES 开发向导. GLSurfaceView是将OpenGL ES图形用到应用的一种方式,对于全屏或者接近全屏的图

Android OpenGL ES绘图教程之六 :响应触摸事件

使对象根据预设的程序进行运动,比如旋转三角形,可以吸引人的注意力.但是如果你想让用户同你的OpenGL ES图形进行交互会怎么样呢?使你的OpenGL ES应用程序触摸互动的关键是要扩展GLSurfaceView,复写onTouchEvent()方法,来监听touch事件.本教程展示了,如何监听透出事件,让用户旋转一个OpenGL ES对象. 1.   设置一个Touch Listener 为了使你的OpenGL ES应用响应touch事件,你必须在GLSurfaceView类中实现OnTouc

Android OpenGL ES绘图教程之三 : 绘制图形

在定义了将要被OpenGL绘制的形状之后,你当然想要绘制它们.使用OpenGL ES 2.0绘制图形需要的代码可能比你想象的要多,因为API提供了大量的图形渲染管道控制接口. 这一章将介绍如何使用OpenGL ES 2.0 API绘制上一章中定义的形状 1. 初始化形状 在你做任何的绘制操作之前,你都必须进行初始化和加载计划绘制的形状.除非在执行的过程中形状所在的结构(原坐标)发生变化,你应该在render中的onSurfaceCreated()方法中初始化它们以提高内存和执行效率. publi

03: OpenGL ES 基础教程02 使用OpenGL ES 基本步骤

第二章:让硬件为你工作(OpenGL ES 应用实践指南 iOS卷) 前言: 1:使用OpenGL ES 基本步骤 2:绘制三角形 3:效果 正文: 一:使用OpenGL ES 基本步骤 1:生成缓存标识符 glGenBuffers(); 2:绑定缓存标识符到当前缓存区域   glBindBuffer(); 3:初始化且分配缓存内存(通常是从CPU控制的内存复制数据到分配的内存)  glBufferData( ); 4:  启用缓存中的数据 glEnableVertexAttribArray()

iOS OpenGL ES简单绘制纹理

OpenGL 中任何复杂的图形都是由点,线 和三角形组成的. 那么一个矩形 就需要有两个三角形组成. 纹理, 可以理解为一张图片, 我么可以将整张or部分图片绘制到圆形, 矩形等目标图形中. 下图表示了顶点数据 对应 的纹理中的点. 左侧代表定点数据, 其坐标原点是屏幕中央 ; 右侧图片(纹理), 坐标原点是左下角 GLKBaseEffect让我们避开了写shader Language 着色器语言, 相当于对glsl的封装 typedef struct { GLKVector3 positonC