betway必威-betway必威官方网站
做最好的网站

【必威官方网站】WebGL学习笔记,技术储备指南

基本的WebGL图形操作(详细参考教程:
绘制三角形 drawElements gl.TRIANGLES
绘制矩形 drawElements 通过绘制两个三角形实现
绘制点 drawElements POINTS
绘制线 drawElements gl.LINE,LINE_STRIP,LINE_LOOP
将绘制的图形填充颜色(使用attribute color,main中传递给varying vColor,在片元着色器中赋值给gl_FragColor)
图形平移(使用uniform location变量,与gl_Position 相加)
图形缩放(使用uniform mat4矩阵,定义缩放大小矩阵,矩阵条目的每行数据乘以顶点的每列数据,矩阵相乘参考:

WebGL 是 HTML 5 草案的一部分,可以驱动 Canvas 渲染三维场景。WebGL 虽然还未有广泛应用,但极具潜力和想象空间。本文是我学习 WebGL 时梳理知识脉络的产物,花点时间整理出来与大家分享。

一. 背景分析
首先我们得有一张全景图片,这个图片是由两个摄像头拍摄合成到一张图片上的全景图片。有了图片,如何在手机上展示,我们使用的是一个球体的模型,想象一下,我们把一张图片贴在一个球面上,这个过程可以认为是我们将一个地球仪上切开,展开成一个长方体的世界地图的逆过程。然后想象我们站在这个球体的中心,通过不断转动这个球体,或者我们调整我们的视线就可以达到看到这个球体上物体全貌的目的。

绘制代码流程记要(此处简单说明变量名及方法名,文章末尾包含了具体测试代码):

示例

二. 技术分析

  1. 声明顶点数组 vertices
  2. 声明顶点索引 indices
  3. 声明颜色数组 colors
  4. 创建顶点/索引/颜色缓冲对象 gl.createBuffer,gl.bindBuffer(gl.ARRARY_BUFFER,new Float32Array(…), gl.STATIC_DRAW),gl.bufferData
  5. 声明GLSL顶点/片段着色器代码
  6. 创建顶点/片段着色器对象并创建着色程序与之关联 gl.createShader(gl.VERTEX_SHADER),gl.attachShader, gl.compileShader,gl.createProgram,gl.attachShader,gl.linkProgram,gl.useProgram
  7. 写入缓冲对象数据到着色程序 gl.getAttribLocation, gl.vertexAttribPointer(position, 3, gl.FLOAT, flase,0,0),gl.getUniformLocation,gl.uniform4f
  8. 绘制图形 gl.clearColor,gl.enable,gl.clear,gl.viewport,gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0),gl.drawArrays(gl.POINTS, 0, vertices.length/3 )

WebGL 很酷,有以下 demos 为证:

  1. 可以和opengl结合使用的相关类。
    (1). 使用GLKViewController和GLKView,优点是使用起来比较简单,缺点是可扩展性和移植性比较差。
    (2). 使用CAEAGLLayer,有点是可以在layer上操作,可移植性高,灵活。
    这里我是在CAEAGLLayer上来做的。
  2. 加载图片文件
    由于图片的组成可能由RGB或者RGBA组成,而且,我们知道能够做为纹理贴图的图片在尺寸上也有要求,所以,我们这里使用ios的自带的类GLKTextureLoader来加载纹理。

后续需要研究:
缩放
旋转
立方体旋转
交互式立方体

寻找奥兹国

三. 代码分析

着重点笔记:
GSSL里的修饰符
attribute 每个顶点数据的顶点着色器与OpenGL ES 之间的链接,每次执行时变化
Uniform 平移位置:使用gl.getUniformLocation获取变量,使用gl.uniform4f(变量,向量) 改变位置,使用gl.uniformMatrix4fv(location, transpose(设置为false),值)设置矩阵
Varying 传递给顶点着色器的变量
GSSL里的数据类型
Vec3 :存储3个值的数组类型
vec4:存储4个值的数组类型
Mat3:存储3个vec3的矩阵类型
Mat4:存储4个vec4的矩阵类型

赛车游戏

  1. 初始化openGL

    <pre>
    self.contentScaleFactor = [[UIScreen mainScreen] scale];

顶点着色器预定义变量
Highp vec4 gl_Position 保存顶点的位置
Mediump float gl_PointSize 保存变换点的大小

划船的男孩(Goo EngineDemo)

片元着色器代码注意添加 Precision mediump float;

本文的目标

CAEAGLLayer *eagLayer = (CAEAGLLayer *)self.layer;
eagLayer.opaque = true;

// 设置layer属性,kEAGLDrawablePropertyRetainedBacking 为No 表示不使用保留背景,告诉core animation不要保留任何以前的图像来重用
// kEAGLColorFormatRGBA8 告诉core animation 用8位来保存层内的每个像素点每个颜色值
eagLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking : [NSNumber numberWithBool:NO],
                                kEAGLDrawablePropertyColorFormat : kEAGLColorFormatRGBA8};

// 使用 openGLES2 来初始化context
_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];

if (!_context ||
    ![EAGLContext setCurrentContext:_context]) {

    NSLog(@"failed to setup EAGLContext");
}

// 生成帧缓存和颜色像素缓存
glGenFramebuffers(1, &_framebuffer);
glGenRenderbuffers(1, &_renderbuffer);

// 将生成帧缓存绑定到帧缓存的位置
glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
// 将生成的颜色渲染缓存绑定到颜色渲染缓存
glBindRenderbuffer(GL_RENDERBUFFER, _renderbuffer);

// 调整颜色渲染缓存的尺寸以匹配layer的新尺寸
[_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer];

// 获取渲染缓存的宽高
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_backingWidth);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_backingHeight);

// 将颜色渲染缓存和帧缓存关联
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderbuffer);

// 检查帧缓存是否创建成功
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {

    NSLog(@"failed to make complete framebuffer object %x", status);
}

// 检查错误信息
GLenum glError = glGetError();
if (GL_NO_ERROR != glError) {

    NSLog(@"failed to setup GL %x", glError);

}

// 加载着色器
if (![self loadShaders]) {
    NSLog(@"加载着色器失败!!!");

}
</pre>

示例GLSL代码:
var vertCode = `
attribute vec3 position;
attribute vec3 color;
varying vec3 vColor;
uniform vec3 translation;
void main(void){
gl_Position = vec4(position, 1.0) vec4(translation, 1.0);
gl_PointSize = 10.0;
vColor = color;
}
`
var fragCode = `
precision mediump float;
varying vec3 vColor;
void main(void){
gl_FragColor = vec4(vColor, 1.0);
}
`

本文的预期读者是:不熟悉图形学,熟悉前端,希望了解或系统学习 WebGL 的同学。

(1). 加载着色器
<pre>
BOOL result = NO;

 

本文不是 WebGL 的概述性文章,也不是完整详细的 WebGL 教程。本文只希望成为一篇供 WebGL 初学者使用的提纲。

GLuint vertShader = 0;
GLuint fragShader = 0;

// 创建一个渲染程序
_program = glCreateProgram();

// 编译顶点着色器
vertShader = compileShader(GL_VERTEX_SHADER, vertexShaderString);

// 编译一个数据为RGB的片元着色器
fragShader = compileShader(GL_FRAGMENT_SHADER, rgbFragmentShaderString);

// 将着色器添加到程序中
glAttachShader(_program, vertShader);
glAttachShader(_program, fragShader);

// 一般使用函数glBindAttribLocation来绑定每个着色器中的attribute变量的位置,然后用函数glVertexAttribPointer为每个attribute变量赋值

测试代码,需要自己写一下html

Canvas

// glBindAttribLocation(_program, ATTRIBUTE_VERTEX, "position");
// glBindAttribLocation(_program, ATTRIBUTE_TEXCOORD, "texcoord");
self.vertexTexCoordAttributeIndex = 3;
glBindAttribLocation(_program, self.vertexTexCoordAttributeIndex, "texcoord");

var canvas = document.getElementById('my_Canvas')
var gl = canvas.getContext('experimental-webgl')

熟悉 Canvas 的同学都知道,Canvas 绘图先要获取绘图上下文:

// 链接着色器程序
glLinkProgram(_program);

// 检查链接的状态
GLint status;
glGetProgramiv(_program, GL_LINK_STATUS, &status);
if (status == GL_FALSE){
    NSLog(@"Failed to link program %d",status);
    goto exit;
}

result = validateProgram(_program);

// 生成一个渲染程序中的Uniform变量的引用
_uniformMatrix = glGetUniformLocation(_program, "modelViewProjectionMatrix");
_uniformSampler = glGetUniformLocation(_program, "s_texture");

var vertices = [
-0.5, 0.5, 0.0,
-0.5, -0.5, 0.0,
0.5, -0.5, 0.0,
]

var context = canvas.getContext('2d');

exit:

var indices = [
0,1,2
]

在context上调用各种函数绘制图形,比如:

if (vertShader)
    glDeleteShader(vertShader);
if (fragShader)
    glDeleteShader(fragShader);

if (result) {

    NSLog(@"OK setup GL programm");

} else {

    glDeleteProgram(_program);
    _program = 0;
}
return result;

var colors = [
0,1,1, ,0,1,1, 0,1,1
]

// 绘制左上角为(0,0),右下角为(50, 50)的矩形

</pre>

var vertex_buffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW)
gl.bindBuffer(gl.ARRAY_BUFFER, null)

context.fillRect(0, 0, 50, 50);

使用的顶点着色器和片元着色器
<pre>
// 声明一个顶点着色器源码和两个片元着色器源码
NSString *const vertexShaderString = SHADER_STRING
(
attribute vec4 position;
attribute vec2 texcoord;
uniform mat4 modelViewProjectionMatrix;
varying vec2 v_texcoord;

var index_buffer = gl.createBuffer()
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buffer)
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW)
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null)

WebGL 同样需要获取绘图上下文:

void main()
{
gl_Position = modelViewProjectionMatrix * position;
v_texcoord = texcoord.xy;
}
);

var color_buffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW)
gl.bindBuffer(gl.ARRAY_BUFFER, null)

var gl = canvas.getContext('webgl'); // 或 experimental-webgl

NSString *const rgbFragmentShaderString = SHADER_STRING
(
varying highp vec2 v_texcoord;
uniform sampler2D s_texture;
void main()
{
gl_FragColor = texture2D(s_texture, v_texcoord);
}
);
</pre>

var vertCode = `
attribute vec3 position;
attribute vec3 color;
varying vec3 vColor;
uniform vec3 translation;

但是接下来,如果想画一个矩形的话,就没这么简单了。实际上,Canvas 是浏览器封装好的一个绘图环境,在实际进行绘图操作时,浏览器仍然需要调用 OpenGL API。而 WebGL API 几乎就是 OpenGL API 未经封装,直接套了一层壳。

  1. 生成创建顶点数据缓存和纹理坐标缓存

uniform mat4 u_xformMatrix;

Canvas 的更多知识,可以参考:

<pre>

void main(void){
gl_Position = ( vec4(position, 1.0) vec4(translation, 1.0) ) * u_xformMatrix ;
gl_PointSize = 10.0;
vColor = color;
}
`
var fragCode = `
precision mediump float;
varying vec3 vColor;
void main(void){
gl_FragColor = vec4(vColor, 1.0);
}
`

JS 权威指南的 21.4 节或JS 高级程序设计中的 15 章

  • (void)setupBuffers {
    GLfloat *vVertices = NULL;
    GLfloat *vTextCoord = NULL;
    GLushort *indices = NULL;
    int numVertices = 0;
    self.numIndices = esGenSphere(SphereSliceNum, SphereRadius, &vVertices, &vTextCoord, &indices, &numVertices);

    //Indices
    glGenBuffers(1, &_vertexIndicesBufferID);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.vertexIndicesBufferID);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, self.numIndices*sizeof(GLushort), indices, GL_STATIC_DRAW);

    // Vertex
    glGenBuffers(1, &_vertexBufferID);
    glBindBuffer(GL_ARRAY_BUFFER, self.vertexBufferID);
    glBufferData(GL_ARRAY_BUFFER, numVertices3sizeof(GLfloat), vVertices, GL_STATIC_DRAW);

    glEnableVertexAttribArray(GLKVertexAttribPosition);
    glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*3, NULL);

    // Texture Coordinates
    glGenBuffers(1, &_vertexTexCoordID);
    glBindBuffer(GL_ARRAY_BUFFER, self.vertexTexCoordID);
    glBufferData(GL_ARRAY_BUFFER, numVertices2sizeof(GLfloat), vTextCoord, GL_DYNAMIC_DRAW);

    glEnableVertexAttribArray(self.vertexTexCoordAttributeIndex);
    glVertexAttribPointer(self.vertexTexCoordAttributeIndex, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*2, NULL);
    }
    </pre>

var vertShader = gl.createShader(gl.VERTEX_SHADER)
gl.shaderSource(vertShader, vertCode)
gl.compileShader(vertShader)

W3CSchool

tips: 使用下面的函数生成球面上顶点坐标和纹理坐标数据
<pre>
//https://github.com/danginsburg/opengles-book-samples/blob/604a02cc84f9cc4369f7efe93d2a1d7f2cab2ba7/iPhone/Common/esUtil.h#L110
int esGenSphere(int numSlices, float radius, float **vertices,
float **texCoords, uint16_t **indices, int *numVertices_out) {
int numParallels = numSlices / 2;
int numVertices = (numParallels 1) * (numSlices 1);
int numIndices = numParallels * numSlices * 6;
float angleStep = (2.0f * ES_PI) / ((float) numSlices);

var fragShader = gl.createShader(gl.FRAGMENT_SHADER)
gl.shaderSource(fragShader, fragCode)
gl.compileShader(fragShader)

阮一峰的 Canvas 教程

if (vertices != NULL) {
    *vertices = malloc(sizeof(float) * 3 * numVertices);
}

if (texCoords != NULL) {
    *texCoords = malloc(sizeof(float) * 2 * numVertices);
}

if (indices != NULL) {
    *indices = malloc(sizeof(uint16_t) * numIndices);
}

for (int i = 0; i < numParallels   1; i  ) {
    for (int j = 0; j < numSlices   1; j  ) {
        int vertex = (i * (numSlices   1)   j) * 3;

        if (vertices) {
            (*vertices)[vertex   0] = radius * sinf(angleStep * (float)i) * sinf(angleStep * (float)j);
            (*vertices)[vertex   1] = radius * cosf(angleStep * (float)i);
            (*vertices)[vertex   2] = radius * sinf(angleStep * (float)i) * cosf(angleStep * (float)j);
        }

        if (texCoords) {
            int texIndex = (i * (numSlices   1)   j) * 2;
            (*texCoords)[texIndex   0] = (float)j / (float)numSlices;
            (*texCoords)[texIndex   1] = 1.0f - ((float)i / (float)numParallels);
        }
    }
}

// Generate the indices
if (indices != NULL) {
    uint16_t *indexBuf = (*indices);
    for (int i = 0; i < numParallels ; i  ) {
        for (int j = 0; j < numSlices; j  ) {
            *indexBuf   = i * (numSlices   1)   j;
            *indexBuf   = (i   1) * (numSlices   1)   j;
            *indexBuf   = (i   1) * (numSlices   1)   (j   1);

            *indexBuf   = i * (numSlices   1)   j;
            *indexBuf   = (i   1) * (numSlices   1)   (j   1);
            *indexBuf   = i * (numSlices   1)   (j   1);
        }
    }
}

if (numVertices_out) {
    *numVertices_out = numVertices;
}
return numIndices;

var shaderProgram = gl.createProgram()
gl.attachShader(shaderProgram, vertShader)
gl.attachShader(shaderProgram, fragShader)
gl.linkProgram(shaderProgram)
gl.useProgram(shaderProgram)

矩阵变换

}
</pre>

//缩放

三维模型,从文件中读出来,到绘制在 Canvas 中,经历了多次坐标变换。

  1. 使用GLKTextureLoader 来加载纹理
    <pre>
    [self.textureloader textureWithCGImage:image options:nil queue:NULL completionHandler:^(GLKTextureInfo *textureInfo, NSError *outError) {
    if (outError){
    NSLog(@"GL Error = %u", glGetError());
    } else {
    if (self.textureInfo.name) {
    GLuint textureName = self.textureInfo.name;
    glDeleteTextures(1, &textureName);
    }
    self.textureInfo = textureInfo;
    }
    }];
    </pre>

  2. 如何开始绘制。这里我使用的是CADisplayLink 这个类来实现
    <pre>
    // 初始化displayLink
    self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkCallback:)];
    self.displayLink.preferredFramesPerSecond = 30;
    [self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    </pre>

  3. 绘制方法
    <pre>
    // 设置上下文
    [EAGLContext setCurrentContext:_context];

    glBindVertexArrayOES(_vertexArrayID);
    // 绑定帧缓存
    glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
    // 设置视口坐标
    glViewport(0, 0, _backingWidth, _backingHeight);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(_program);

    if (self.textureInfo != nil){
    // 绑定纹理到缓存
    glBindTexture(GL_TEXTURE_2D, self.textureInfo.name);

     [self update];
    
     glDrawElements(GL_TRIANGLES, self.numIndices, GL_UNSIGNED_SHORT, 0);
    

/*
三角形的3个顶点坐标mat4
vertices = [
-0.5, 0.5, 0.0, 1.0 //第1个顶点 ,在左上角,注意这里是1个gl_Position的position
-0.5, -0.5, 0.0, 1.0 //第2个顶点, 在左下角
0.5, -0.5, 0.0, 1.0 //第3个顶点,在右下角
]
第1个顶点坐标的vec4
position = [-0.5, 0.5, 0.0, 1.0]

假设有一个最简单的模型:三角形,三个顶点分别为(-1,-1,0),(1,-1,0),(0,1,0)。这三个数据是从文件中读出来的,是三角形最初始的坐标(局部坐标)。如下图所示,右手坐标系。

// glDrawArrays(GL_TRIANGLES, 0, sphereVertices);

缩放矩阵mat4与 顶点坐标vec4相乘,实现缩放
position * u_xformMatrix;
*/

必威官方网站 1

}

glBindRenderbuffer(GL_RENDERBUFFER, _renderbuffer);
[_context presentRenderbuffer:GL_RENDERBUFFER];

var x = 1.5, y=1.5, z= 1
var xformMatrix = [
x, 0.0, 0.0, 0.0, // 乘顶点数据条目的第1个坐标位置
0.0, y, 0.0, 0.0, // 乘顶点数据条目的第2个坐标位置
0.0, 0.0, z, 0.0, // 乘顶点数据条目的第3个坐标位置
0.0, 0.0, 0.0, 1.0, // 此处相乘的是gl_Position里定义的一个1.0固定值, 即最后1个顶点的数组键值
]
/*
最终得到第1个顶点坐标的缩放结果为:
position = [-0.75, 0.75, 0.0 ,1.0 ],得到 第1个顶点的x轴向左移动,y轴向上移动,实现了三角形的缩放变大
即xformMatrix的每行行(x,y,z轴新坐标)与 vertices的每列数据(每个顶点的x,y,z轴坐标)相乘,实现了缩放

模型通常不会位于场景的原点,假设三角形的原点位于(0,0,-1)处,没有旋转或缩放,三个顶点分别为(-1,-1,-1),(1,-1,-1),(0,1,-1),即世界坐标。

}
</pre>

*/

必威官方网站 2

这里我们做完上面的步骤,如果只是在顶点着色器中传入一个普通的单位矩阵,我们得到 的只是一个站在球体外面看到的球体的一部分,就好像我们站在太空上观察这个地球一样,所以我们要通过透视投影矩阵和各种变换矩阵,来达到我们上面说到的让我们的视线能够处在圆心处观察。

var u_xformMatrix = gl.getUniformLocation(shaderProgram, 'u_xformMatrix')
gl.uniformMatrix4fv(u_xformMatrix, false, new Float32Array(xformMatrix))

绘制三维场景必须指定一个观察者,假设观察者位于(0,0,1)处而且看向三角形,那么三个顶点相对于观察者的坐标为(-1,-1,-2),(1,-1,-2),(0,1,-2),即视图坐标。

分析代码:
<pre>
float aspect = fabs(self.bounds.size.width / self.bounds.size.height);
// 生成透视投影矩阵
GLKMatrix4 projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(self.overture), aspect, 0.1f, 400.0f);

gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer)
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buffer)
var position = gl.getAttribLocation(shaderProgram, 'position')
gl.vertexAttribPointer(position, 3, gl.FLOAT, false, 0, 0)
gl.enableVertexAttribArray(position)

必威官方网站 3

// 将y轴翻转
projectionMatrix = GLKMatrix4Rotate(projectionMatrix, ES_PI, 1.0f, 0.0f, 0.0f);

GLKMatrix4 modelViewMatrix = GLKMatrix4Identity;

 modelViewMatrix = GLKMatrix4RotateX(modelViewMatrix, 0.0f);
 modelViewMatrix = GLKMatrix4RotateY(modelViewMatrix, 0.0f);

self.modelViewProjectionMatrix = GLKMatrix4Multiply(projectionMatrix, modelViewMatrix);

glUniformMatrix4fv(_uniformMatrix, 1, GL_FALSE, self.modelViewProjectionMatrix.m);

gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer)
var color = gl.getAttribLocation(shaderProgram, 'color')
gl.vertexAttribPointer(color, 3, gl.FLOAT, false, 0, 0)
gl.enableVertexAttribArray(color)
//平移
var translation = gl.getUniformLocation(shaderProgram, 'translation')
//会对vertices的3个顶点的x,y,z坐标进行相加实现平移,比如
/*
原vertices 顶点的坐标如下:
position = [
-0.5, 0.5, 0.0, 1.0 第1个顶点,相加后变成了 -0.3, 0.4, -0.1 即 第1个顶点的x轴向右平移 y轴向下平移
-0.5, -0.5, 0.0, 1.0 第2个顶点,相加后变成了 -0.3, -0.6, -0.1 即 第2个顶点的x轴向右平移 y轴向下平移
0.5, -0.5, 0.0, 1.0 第3个顶点,相加后变成了 0.7, -0.6, -0.1 即 第3个顶点的x轴向右平移 y轴向下平移
]
这样三角形的3个顶点都进行的平移
*/
gl.uniform3f(translation, 0.2,-0.1,-0.1)

观察者的眼睛是一个点(这是透视投影的前提),水平视角和垂直视角都是90度,视野范围(目力所及)为[0,2]在Z轴上,观察者能够看到的区域是一个四棱台体。

</pre>

gl.clearColor(1.0, 1.0, 1.0, 1.0)
gl.enable(gl.DEPTH_TEST)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.viewport(0, 0, canvas.width, canvas.height)

必威官方网站 4

gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0)

将四棱台体映射为标准立方体(CCV,中心为原点,边长为2,边与坐标轴平行)。顶点在 CCV 中的坐标,离它最终在 Canvas 中的坐标已经很接近了,如果把 CCV 的前表面看成 Canvas,那么最终三角形就画在图中橙色三角形的位置。

必威官方网站 5

上述变换是用矩阵来进行的。

局部坐标 –(模型变换)-> 世界坐标 –(视图变换)-> 视图坐标 –(投影变换)–> CCV 坐标。

以(0,1,0)为例,它的齐次向量为(0,0,1,1),上述变换的表示过程可以是:

必威官方网站 6

上面三个矩阵依次是透视投影矩阵,视图矩阵,模型矩阵。三个矩阵的值分别取决于:观察者的视角和视野距离,观察者在世界中的状态(位置和方向),模型在世界中的状态(位置和方向)。计算的结果是(0,1,1,2),化成齐次坐标是(0,0.5,0.5,1),就是这个点在CCV中的坐标,那么(0,0.5)就是在Canvas中的坐标(认为 Canvas 中心为原点,长宽都为2)。

上面出现的(0,0,1,1)是(0,0,1)的齐次向量。齐次向量(x,y,z,w)可以代表三维向量(x,y,z)参与矩阵运算,通俗地说,w 分量为 1 时表示位置,w 分量为 0 时表示位移。

WebGL 没有提供任何有关上述变换的机制,开发者需要亲自计算顶点的 CCV 坐标。

关于坐标变换的更多内容,可以参考:

计算机图形学中的5-7章

变换矩阵@维基百科

透视投影详解

比较复杂的是模型变换中的绕任意轴旋转(通常用四元数生成矩阵)和投影变换(上面的例子都没收涉及到)。

关于绕任意轴旋转和四元数,可以参考:

四元数@维基百科

一个老外对四元数公式的证明

关于齐次向量的更多内容,可以参考。

计算机图形学的5.2节

齐次坐标@维基百科

着色器和光栅化

在 WebGL 中,开发者是通过着色器来完成上述变换的。着色器是运行在显卡中的程序,以 GLSL 语言编写,开发者需要将着色器的源码以字符串的形式传给 WebGL 上下文的相关函数。

着色器有两种,顶点着色器和片元(像素)着色器,它们成对出现。顶点着色器任务是接收顶点的局部坐标,输出 CCV 坐标。CCV 坐标经过光栅化,转化为逐像素的数据,传给片元着色器。片元着色器的任务是确定每个片元的颜色。

顶点着色器接收的是 attribute 变量,是逐顶点的数据。顶点着色器输出 varying 变量,也是逐顶点的。逐顶点的 varying 变量数据经过光栅化,成为逐片元的 varying 变量数据,输入片元着色器,片元着色器输出的结果就会显示在 Canvas 上。

必威官方网站 7

着色器功能很多,上述只是基本功能。大部分炫酷的效果都是依赖着色器的。如果你对着色器完全没有概念,可以试着理解下一节 hello world 程序中的着色器再回顾一下本节。

关于更多着色器的知识,可以参考:

GLSL@维基百科

本文由betway必威发布于网页设计,转载请注明出处:【必威官方网站】WebGL学习笔记,技术储备指南

Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。