【笔记】《WebGL编程指南》学习(9)

WebGL编程指南学习(9)

Ragnarok!

9. 高级技巧(续)

α \alpha α混合

如何实现 α \alpha α混合

两个步骤

  1. 开启混合功能gl.enable(gl.BLEND)
  2. 指定混合函数gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)

混合函数

gl.blendFund()在进行 α \alpha α混合时,实际上用到了两个颜色,源颜色(source color)和目标颜色(destination color)。前者是“待被混合的颜色”,也就是原本的颜色;后者是“待被混合进去的颜色”。

gl.blendFunc(src_factor, dst_factor)
// 混合后颜色=源颜色*src_factor+目标颜色*dst_factor

这里的src_factor和dst_factor是有特指的,比如gl.ZERO,gl.ONE_MINUS_DST_COLOR

半透明的三维物体

  • α \alpha α混合发生在绘制片元的过程。当隐藏面消除功能开启时,被隐藏的片元不会被绘制,所以也就不会发生混合过程,更不会有半透明的效果。只要关闭隐藏面消除就可以达到半透明的效果
  • 但是关闭隐藏面消除功能只是一个粗暴的解决方案。实际绘制三维场景时,场景中往往既有不透明的物体,也有半透明的物体,如果关闭隐藏面消除功能,那些不透明物体的前后关系就会乱套

同时实现隐藏面消除和半透明效果的机制

  1. 开启隐藏面消除
  2. 绘制所有不透明的物体
  3. 锁定用于进行隐藏面消除的深度缓冲区的写入操作,使之只读
  4. 绘制所有半透明的物体,注意它们应当按照深度排序,然后从后向前绘制
  5. 释放深度缓冲区,使之可读可写
gl.depthMask(mask)
// 锁定或释放深度缓冲区的写入操作,true/false

由于深度缓冲区被锁定了,绘制不透明物体后面的物体,即使是半透明的也不会显示;而深度在不透明物体前面的,会正常显示不透明的效果

切换着色器

对不同的物体,经常需要使用不同的着色器来绘制。每个着色器中可能有非常复杂的逻辑以实现各种不同的效果。我们可以准备多个着色器,然后根据需要来切换它们

渲染到纹理

使用WebGL渲染三维图形,然后将渲染结果作为纹理贴到另一个三维物体上去。

帧缓冲区对象和渲染缓冲区对象

默认情况下,WebGL在颜色缓冲区中进行绘图,在开启隐藏面消除功能室,还会用到深度缓冲区。总之,绘制的结果图像是存储在颜色缓冲区中的。

帧缓冲区对象(framebuffer object)可以代替颜色缓冲区或深度缓冲区。绘制在帧缓冲区中的对象并不会直接显示在canvas上。因此,我们就可以先对帧缓冲区中的内容进行一些处理再显示;或者直接用其中的内容作为纹理图像。

在帧缓冲区中进行绘制的过程又称为离屏绘制(offscreen drawing)

帧缓冲区提供了颜色缓冲区和深度缓冲区的替代品——所关联的对象(attachment)。一个帧缓冲区有3个关联对象:颜色关联对象(color attachment)、深度关联对象(depth attachment)和模板关联对象(stencil attachment),分别用来替代颜色缓冲区、深度缓冲区和模板缓冲区

  • 经过一些设置,WebGL可以向帧缓冲区的关联对象写入数据
  • 每个关联对象又可以是两种类型的:纹理对象渲染缓冲区对象

如何实现渲染到纹理?

如果希望把WebGL渲染出的图像作为纹理使用,那么就需要:

  1. 将纹理对象作为颜色关联对象关联到帧缓冲区对象上;
  2. 在帧缓冲区中进行绘制

此时,颜色关联对象(即纹理对象)就替代了颜色缓冲区。由于此时仍然需要进行隐藏面消除,所以又创建一个渲染缓冲区对象来作为帧缓冲区的深度关联对象,以替代深度缓冲区

所以,最终帧缓冲区的配置是这样的——

颜色关联对象——纹理对象

深度关联对象——渲染缓冲区对象

注意这两个对象的绘图区域大小必须一致

例程:将一个旋转的立方体作为纹理贴在一个矩形上

算法步骤

  1. 创建帧缓冲区对象
  2. 创建纹理对象并设置其尺寸和参数
  3. 创建渲染缓冲区对象
  4. 绑定渲染缓冲区对象并设置其尺寸
  5. 将帧缓冲区的颜色关联对象指定为一个纹理对象
  6. 将帧缓冲区的深度关联对象指定为一个渲染缓冲区对象
  7. 检查帧缓冲区是否配置正确
  8. 在帧缓冲区进行绘制

前调

// 着色器
...
// 设置离屏绘制的尺寸
var OFFSCREEN_WIDTH = 256;
var OFFSCREEN_HEIGHT = 256;

function main() {
  // 一些上下文设置
  ...
  // 设置顶点信息
  var cube = initVertexBufferForCube(gl);
  var plane = initVertexBufferForPlane(gl);
  ...
  // 设置纹理
  var texture = initTextures(gl);
  ...
  // 初始化帧缓冲区(Framebuffer object,FBO)
  var fbo = initFramebufferObject(gl);
  ...
  var viewProjMatrix = new Matrix4(); // 为颜色缓冲区所准备
  viewProjMatrix.setPerspective(30, canvas.width/canvas.height, 1.0, 100.0);
  viewProjMatrix.lookAt(0.0, 0.0, 7.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
  
  var viewProjMatrixFBO = new Matrix4(); // 为帧缓冲区所准备
  viewProjMatrixFBO.setPerspective(30.0, OFFSCREEN_WIDTH/OFFSCREEN_HEIGHT, 1.0, 100.0);
  viewProjMatrixFBO.lookAt(0.0, 2.0, 7.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
  // 开始绘制
  var currentAngle = 0.0;
  var tick = function() {
    currentAngle = animate(currentAngle);
    draw(gl, canvas, fbo, plane, cube, currentAngle, texture, viewProjMatrix, viewProjMatrixFBO);
    window.requestAnimationFrame(tick, canvas);
  };
  tick();
}

function initVertexBuffersForCube(gl) {
  // 创建一个cube
}
  
function initVertexBuffersForPlane(gl) {
  // 创建一个face
}

function initArrayBufferForLaterUse(gl, data, num, type) {
  // 创建一个buffer object
}

function initElementArrayBufferForLaterUse(gl, data, type) {
  // 创建一个buffer object
} 

创建帧缓冲区对象、创建纹理对象并设置其尺寸和参数、创建渲染缓冲区对象并设置其尺寸和参数、将纹理对象关联到帧缓冲区

function initFramebufferObject(gl) {
  var frameBuffer, texture, depthBuffer;
  ...
  // 创建一个FBO
  framebuffer = gl.createFramebuffer();
  ...
  // 创建一个纹理对象并设置其尺寸和参数
  texture = gl.createTexture();
  ...
  gl.bindTexture(gl.TEXTURE_2D, texture);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
  gl.texOarameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  framebuffer.texture = texture; // 保存纹理对象
  // 创建一个渲染缓冲区对象(renderbuffer object),并设置其尺寸和参数
  depthBuffer = gl.createRenderbuffer();
  ...
  gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
  gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT);
  // 把纹理和渲染缓冲区对象关联到FBO
  gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
  gl.framebuferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
  gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);
  // 检查FBO是否配置正确
  var e = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
  ...
}

渲染缓冲区的设置

gl.renderbufferStorage(target, internalformat, width, height);

其中,internalformat指定渲染缓冲区中的数据格式,包括gl.DEPTH_COMPONENT16(表示渲染缓冲区将替代深度缓冲区)、gl.STENCIL_INDEX8(表示渲染缓冲区将替代模板缓冲区)、gl.RGBA4(表示渲染缓冲区将替代颜色缓冲区),等

关联纹理对象到FBO

gl.framebufferTexture2D(target, attachment, textarget, texture, level);
// 参数:
// target:必须是gl.FRAMEBUFFER
// attachent: 
// 	gl.COLOR_ATTACHMENT0 颜色关联对象
//	gl.DEPTH_ATTACHMENT 深度关联对象
// textarget: 纹理类型gl.TEXTURE_2D|gl.TEXTURE_CUBE
// texture: 要关联的纹理对象
// level: 0, 使用mipmap纹理时指定纹理的层级

关联帧缓冲区对象到FBO

gl.framebufferRenderbuffer(target, attachment, renderbuffertarget, renderbuffer);
// 参数
// attachment:
//	gl.COLOR_ATTACHMENT0: 颜色关联对象
//	gl.DEPTH_ATTACHMENT:	深度关联对象
//	gl.STENCIL_ATTACHMENT:模板关联对象

在FBO进行绘图

  • 首先把绘制目标切换为FBO,并在其颜色关联对象,即纹理对象中绘制立方体;
  • 然后把绘制目标切换回canvas,在颜色缓冲区中绘制矩形,同时把上一步在纹理对象中绘制的图像贴到矩形表面上
function draw(gl, canvas, fbo, plane, cube, angle, texture viewProjMatrix, viewProjMAtrixFBO) {
  // 切换渲染目标为FBO
  gl.bindFrameBuffer(gl.FRAMEBUFFER, fbo);
  gl.viewport(0, 0, OFFSCREEN_WIDTH, OFFSCREEEN_HEIGHT);
  gl.clearColor(0.2, 0.2, 0.4, 1.0);
  gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT);
  // 渲染立方体
  drawTexturedCube(gl, gl.program, cube, angle, texture, viewProjMatrixFBO);
  
  // 切换渲染目标为颜色缓冲区,实际上解绑帧缓冲区
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  gl.viewport(0, 0, canvas,width, canvas.height);
  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT);
  // 渲染平面
  drawTexturedPlane(gl, gl.program, plane, angle, fbo.texture, viewProjMatrix);
}

注意fbo.texture作为参数传入了drawTexturedPlane,供绘制平面是使用。fbo.texture是离屏绘制结果的纹理对象

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值