看这篇之前,建议先看之前几篇,这几篇是基础。
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类中最关键的就是完成绘图,我们重点也是分析这个函数。
绘图用的颜色着色器
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
绘图用的顶点着色器
参数类型说明:
- 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 来定位。
这个函数输出参数的坐标系是手机屏幕的坐标系,如下图:
在 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跟坐标轴并不对齐。
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)
这个坐标转换请看下图:
Next, solve for the affine transformation matrix
下一步,解决 仿射变换矩阵 (仿射变换(Affine Transformation))
有关 仿射变换矩阵 的知识请参看: http://www.cnblogs.com/ghj1976/p/5199086.html
我们现在要通过计算 A点通过一个矩阵转换成 P 点坐标, B点转换成 Q点坐标, D点转换成 S点坐标,如下图:
这个转换肯定是一个 仿射变换 。
A点的坐标是 (-1,+1) P 的坐标是 (2*PX2, 2*PY2) ,这个转换可以用下面公式表示。
同理有 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这个转换矩阵应该是
传递给OpenGL的uvp 的矩阵值的计算
这个的计算逻辑过程跟 mvp的过程一样, 不过是 屏幕坐标系 跟
贴图纹理的坐标是类似的, 不过在纹理坐标系中, ABCD 的坐标是:
// (0,0) (1,0)
// (0,1) (1,1)
PQRS 四个轴跟坐标系是对齐的。如下图效果所示:
由于是轴对齐的, 所以存在:
px=sx qy=py
跟上面一样做 A到P的转换 和 B到Q的转换。 矩阵运算如下:
最后可以得到这个转换矩阵为,这也是 uvp 传入的值。
给 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
https://github.com/fem-d/webGL/tree/master/blog
数据流程图
顶点数据传递流程图
颜色渲染数据传递流程图
参考:
OpenGL ES 2 封装库说明
https://godoc.org/golang.org/x/mobile/gl
glutil 包说明文档
https://godoc.org/golang.org/x/mobile/exp/gl/glutil
有疑问加站长微信联系(非本文作者)