导航

golang.org/x/mobile/exp/gl/glutil/glimage.go 源码分析

Posted on 2016-02-19 09:01  蝈蝈俊  阅读(1295)  评论(0编辑  收藏  举报

看这篇之前,建议先看之前几篇,这几篇是基础。

Go Mobile 例子 basic 源码分析
http://www.cnblogs.com/ghj1976/p/5183199.html

OpenGL ES 着色语言
http://www.cnblogs.com/ghj1976/p/5180895.html

仿射变换矩阵(这是glimage包最核心的矩阵算法变换逻辑基础知识)
http://www.cnblogs.com/ghj1976/p/5199086.html 

 

glutil 包中重要的类是 Image 类, 它实现了 *image.RGBA 和 OpenGL纹理图形之间的桥梁,而Image类中最关键的就是完成绘图,我们重点也是分析这个函数。

 

绘图用的颜色着色器

image_thumb4

 

precision用来确定默认精度修饰符,precision mediump float; 基本相当于中等精度。

varying 标示是从顶点着色器传递过来的变量。 vec2  2个浮点的向量  UV

uniform 只读变量  sampler2D

sampler2D是个啥?其实在CG中,sampler2D就是和texture所绑定的一个数据容器接口。这个说法还是太复杂了,简单理解的话,所谓加载以后的texture(贴图)说白了不过是一块内存存储的,使用了RGB(也许还有A)通道,且每个通道8bits的数据。而具体地想知道像素与坐标的对应关系,以及获取这些数据,我们总不能一次一次去自己计算内存地址或者偏移,因此可以通过sampler2D来对贴图进行操作。更简单地理解,sampler2D就是GLSL中的2D贴图的类型,相应的,还有sampler1D,sampler3D,samplerCube等等格式。

texture2D(textureSample, UV) 通过texture2D函数我们可以得到一个纹素(texel),这是一个纹理图片中的像素。函数参数分别为simpler2D以及纹理坐标。

参考: http://blog.csdn.net/racehorse/article/details/6664717

 

绘图用的顶点着色器

image

参数类型说明:

  • uniform 只读的变量
  • mat3  3*3 的浮点矩阵。
  • attribute 专用于顶点着色器的只读变量
  • vec3 三个浮点的向量、 vec2 2个浮点的向量。
  • varying 标示是要从顶点着色器传递的变量,具体变量名字是UV, 后面有UV的计算公式。

gl_Position 相关计算

gl_Position 变量是一个四维 (vec4) 变量,包含顶点的 x、y、z 和 w 值。上面代码中 w 值恒定为 1,其他值为 mvp * p 计算出来。

mvp 是坐标转换的矩阵,具体计算逻辑请看后面。

p是 z 轴恒定为1 的 三维变量, 其他两维 由 pos 参数传入。

pos 是由 quadXYCoords 传入的,它是 OpenGl 坐标系的四个顶点坐标。

 

UV 顶点对应颜色计算

在颜色着色器中, 颜色和位置的对应关系由UV传入。 UV 的计算在顶点着色器中完成计算。

UV 由 uvp 转换矩阵 * 具体坐标 inUV 计算而来。

inUV 传递的值是由 quadUVCoords 传入的,它是 手机屏幕坐标系的坐标四个顶点。

 

绘图函数参数分析


绘制具体图我们用的下面封装的函数
golang.org/x/mobile/exp/gl/glutil/glimage.go

// Draw draws the srcBounds part of the image onto a parallelogram, defined by
// three of its corners, in the current GL framebuffer.
func (img *Image) Draw(sz size.Event, topLeft, topRight, bottomLeft geom.Point, srcBounds image.Rectangle) {

这个函数通过给定的三个点(左上角、右上角、左下角)来画出一个平行四边形。

  • img  要绘制的图片类。
  • 这个函数的第一个参数 sz size.Event 是游戏屏幕的大小。
  • topLeft, topRight, bottomLeft geom.Point  是确定平行四边形的三个点。
  • srcBounds image.Rectangle 是要绘图的位置边界值,即在哪里绘制这个图。 通过两个点 Min, Max Point 来定位。

这个函数输出参数的坐标系是手机屏幕的坐标系,如下图:

image_thumb1

在 golang.org/x/mobile/geom 包中有 pixel 和 pt 的转换函数。

px是像素,屏幕中最小元素单位。
pt是磅数,1英寸为72磅。字体大小的单位一般用磅数。

 

传递给OpenGL的 mvp 矩阵值的计算

We are drawing a parallelogram PQRS, defined by three of its corners, onto the entire GL framebuffer ABCD.
我们在整个OpenGL帧缓冲ABCD中画一个由三个角确定的平行四边形 PQRS。

The two quads may actually be equal, but in the general case, PQRS can be smaller, and PQRS is not necessarily axis-aligned.
这两个四边形实际上是相等的,但一般情况下PQRS会更小,而且PQRS跟坐标轴并不对齐。

 

image

There are two co-ordinate spaces: geom space and framebuffer space.
这里我们会用到2个坐标系, geom坐标系和 Framebuffer 坐标系。

In geom space, the ABCD rectangle is:
在 geom 坐标系中, ABCD的矩形坐标如下:

    (0, 0)                               (geom.Width, 0)
    (0, geom.Height)       (geom.Width, geom.Height)

and the PQRS quad is:  PQRS 的坐标是:

    (topLeft.X,    topLeft.Y)                         (topRight.X, topRight.Y)
    (bottomLeft.X, bottomLeft.Y)           (implicit,   implicit)

In framebuffer space, the ABCD rectangle is:
在 framebuffer 坐标系下,ABCD 矩形坐标是

    (-1, +1) (+1, +1)
    (-1, -1)  (+1, -1)

First of all, convert from geom space to framebuffer space.
首先,我们需要从 geom 坐标系转变成 framebuffer 坐标系
For later convenience, we divide everything by 2 here: px2 is half of the P.X co-ordinate (in framebuffer space).
为了后面方便起见,我们这里2分一切,px2是PX坐标系实际坐标的一半(framebuffer坐标系)

px2 := -0.5 + float32(topLeft.X/sz.WidthPt)
py2 := +0.5 - float32(topLeft.Y/sz.HeightPt)
qx2 := -0.5 + float32(topRight.X/sz.WidthPt)
qy2 := +0.5 - float32(topRight.Y/sz.HeightPt)
sx2 := -0.5 + float32(bottomLeft.X/sz.WidthPt)
sy2 := +0.5 - float32(bottomLeft.Y/sz.HeightPt)

这个坐标转换请看下图:

image

Next, solve for the affine transformation matrix
下一步,解决 仿射变换矩阵 (仿射变换(Affine Transformation))

有关 仿射变换矩阵 的知识请参看: http://www.cnblogs.com/ghj1976/p/5199086.html 

我们现在要通过计算 A点通过一个矩阵转换成 P 点坐标, B点转换成 Q点坐标, D点转换成 S点坐标,如下图:

image

这个转换肯定是一个 仿射变换 。 

A点的坐标是 (-1,+1)   P 的坐标是 (2*PX2, 2*PY2)  ,这个转换可以用下面公式表示。

image

同理有 B –> Q , D->S 的 公式。

从公式中提取6个表达式如下:

-a00 + a01 + a02 = 2*px2
-a10 + a11 + a12 = 2*py2
+a00 + a01 + a02 = 2*qx2
+a10 + a11 + a12 = 2*qy2
-a00 - a01 + a02 = 2*sx2
-a10 - a11 + a12 = 2*sy2

通过合并运算,可以推理得到

a00 = qx2 - px2
a01 = px2 - sx2
a02 = qx2 + sx2
a10 = qy2 - py2
a11 = py2 - sy2
a12 = qy2 + sy2       

即mvp这个转换矩阵应该是

image

 

传递给OpenGL的uvp 的矩阵值的计算

这个的计算逻辑过程跟 mvp的过程一样, 不过是 屏幕坐标系 跟

贴图纹理的坐标是类似的, 不过在纹理坐标系中, ABCD 的坐标是:

        //    (0,0) (1,0)
        //    (0,1) (1,1)

PQRS 四个轴跟坐标系是对齐的。如下图效果所示:

image

由于是轴对齐的, 所以存在:

px=sx     qy=py

跟上面一样做 A到P的转换  和  B到Q的转换。 矩阵运算如下:

image

最后可以得到这个转换矩阵为,这也是 uvp 传入的值。

image

 

 

给 OpenGL 传递顶点数据

 

下面这三行代码经常一起出现,含义解释如下:

    glctx.BindBuffer(gl.ARRAY_BUFFER, glimage.quadXY)
    glctx.EnableVertexAttribArray(glimage.pos)
    glctx.VertexAttribPointer(glimage.pos, 2, gl.FLOAT, false, 0, 0)

BindBuffer(target Enum, b Buffer)
的第一个参数是表明buffer的类型,gl.ARRAY_BUFFER表明存储vertex data。
绑定了buffer后,我们需要为buffer传值,传值在之前完成 BufferData 这里完成。
 
EnableVertexAttribArray(a Attrib)

激活该属性

 

VertexAttribPointer(dst Attrib, size int, ty Enum, normalized bool, stride, offset int)

  • dst: 需要绑定的属性的索引值。
  • size: 表示buffer中几个值代表一个vertex。
  • ty: 表示buffer中数据的类型,一般有FIXED, BYTE, UNSIGNED_BYTE, FLOAT, SHORT, UNSIGNED_SHORT。
  • normalized: 这个参数可以设为true或者false,它涉及到数据转换。一般我们都将它设为false。
  • stride: 如果为0,表示buffer中的数据是按顺序存储。
  • offset: buffer的偏移值,如果设置了则会从Offset的位置开始。

整个这个过程可以用下图表示,图来自:  https://github.com/fem-d/webGL/blob/master/blog/WebGL%E5%9F%BA%E7%A1%80%E5%AD%A6%E4%B9%A0%E7%AF%87%EF%BC%88Lesson%202%EF%BC%89.md

image

 

https://github.com/fem-d/webGL/tree/master/blog

 

 

数据流程图

顶点数据传递流程图

 

image

 

颜色渲染数据传递流程图

image

 

纹理操作

BindTexture(target Enum, t Texture)

为了把一个名为texture的纹理对象绑定为一个2D纹理对象。  p.glctx.BindTexture(gl.TEXTURE_2D, img.gltex)

TexImage2D(target Enum, level int, width, height int, format Enum, ty Enum, data []byte)

    • target   操作的目标类型,设为 GL_TEXTURE_2D 即可
    • level   纹理的级别,这里设置成了0 。表示多级分辨率的纹理图像的级数,若只有一种分辨率,则level设为0。
    • width和height ---- 给出了纹理图像的长度和宽度,纹理映射的最大尺寸依赖于OpenGL,但它至少必须是使用64x64(若带边界为66x66),若width和height设置为0,则纹理映射有效地关闭。
必须是2的n次方。纹理图片至少要支持64个材质元素的宽度
    • format和ty ---- 描述了纹理映射的格式和数据类型
//format    像素数据的颜色格式,必须和internalformatt取值必须相同。可选的值有
// GL_ALPHA,
// GL_RGB,
// GL_RGBA,
// GL_LUMINANCE,
// GL_LUMINANCE_ALPHA 等几种。


//type 指定像素数据的数据类型。可以使用的值有
// GL_UNSIGNED_BYTE,
// GL_UNSIGNED_SHORT_5_6_5,
// GL_UNSIGNED_SHORT_4_4_4_4,
// GL_UNSIGNED_SHORT_5_5_5_1
  • data 告诉OpenGL纹理数据的来源
TexParameteri(target, pname Enum, param int)
指定纹理格式。这里包括纹理横向和纵向的重复方式,这里我们看到设置了4个。
    p.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
    p.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
    p.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
    p.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)

TEXTURE_MAG_FILTER
TEXTURE_MIN_FILTER

当图片缩放时就需要重新计算每一像素的颜色,而缩放后的图片和原图的像素是无法对上的,因此颜色的分配就有很多算法。最基本的两种算法是“就近取色(NEAREST)”和“线性插值(LINEAR)”,这也是“TEXTURE_MAG_FILTER”和“TEXTURE_MIN_FILTER”的值。下面咱也来说说这个两个值。
  NEAREST:把原图在缩放后映射过去的像素直接使用,也就是对映射坐标取近似值,让每一个坐标都对应到像素上。这个做法可以确保颜色表不被破坏,缩放后使用的颜色表绝不会超出原图的颜色表。而且计算量小,所以速度很快。
  LINEAR:取映射坐标最接近的几个像素,把他们的颜色做平均值计算。这样缩放后颜色表就会和原图的不同,不过由于取了平均值,使得颜色的过度更加自然,可以有效的消除锯齿。但是由于需要计算颜色平均值,计算量就变高的,速度就稍为逊色。

下面是效果图,这个东西比较精细,可能很难看清,仔细看文字的边缘

image

“TEXTURE_WRAP_S”和“TEXTURE_WRAP_T”。这两个东西是用来指定图片平铺的。“TEXTURE_WRAP_S”是横向的设置,“TEXTURE_WRAP_T”是纵向的设置。他们有三个值:
    REPEAT
    MIRRORED_REPEAT
    CLAMP_TO_EDGE

这三个值很容易理解的,测试也很容易看出结果。贴图默认都是平铺的,也就是第一个值。如果我们需要不平铺就可以设置成“CLAMP_TO_EDGE”,就像CSS中的“no-repeat”一样。中间“MIRRORED_REPEAT”也是平铺,只不过是正反交替的。看下面的截图就很容易明白。

image

参考: https://www.web-tinker.com/article/20163.html

TexSubImage2D(target Enum, level int, x, y, width, height int, format, ty Enum, data []byte)

writes a subregion of a 2D texture image.
 

 

 

参考:

OpenGL ES 2 封装库说明
https://godoc.org/golang.org/x/mobile/gl

glutil 包说明文档
https://godoc.org/golang.org/x/mobile/exp/gl/glutil

WebGL基础学习篇
https://github.com/fem-d/webGL/blob/master/blog/WebGL%E5%9F%BA%E7%A1%80%E5%AD%A6%E4%B9%A0%E7%AF%87%EF%BC%88Lesson%202%EF%BC%89.md