半透明与深度前言
- 计算机图形学中通常由红色、绿色、蓝色和alpha通道组成
- alpha通道,主要用来控制透明
创建项目
绘制小绿人
- 声明三个变量
- 声明定义一个函数buildMesh(),以便使我们的小绿人不是全屏整个窗口,使网格放置在屏幕上某一个位置,后续学习了移动网格就不需要这么麻烦了
ofMesh charMesh;
ofShader shader;
ofImage img;
void buildMesh(ofMesh& mesh, float w, float h, glm::vec3 pos)
{
float verts[] = { -w + pos.x ,-h + pos.y,pos.z,
-w + pos.x,h + pos.y,pos.z,
w + pos.x,h + pos.y,pos.z,
w + pos.x,-h + pos.y,pos.z
};
float uvs[] = { 0,0, 0,1, 1,1, 1,0 };
for (int i = 0; i < 4; i++)
{
int idx = i * 3;
int uvIdx = i * 2;
mesh.addVertex(glm::vec3(verts[idx], verts[idx + 1], verts[idx + 2]));
mesh.addTexCoord(glm::vec2(uvs[uvIdx], uvs[uvIdx + 1]));
}
ofIndexType indices[6] = { 0,1,2,2,3,0 };
mesh.addIndices(indices, 6);
}
void ofApp::setup(){
ofDisableArbTex();
buildMesh(charMesh, 0.25, 0.5, glm::vec3(0.0, 0.15, 0.0));
img.load("alien.png");
shader.load("passthrough.vert", "alphaTest.frag");
}
void ofApp::draw(){
shader.begin();
shader.setUniformTexture("greenMan",img,0);
charMesh.draw();
shader.end();
}
#version 410
layout (location = 0) in vec3 pos;
layout (location = 3) in vec2 uv;
out vec2 fragUV;
void main()
{
gl_Position = vec4(pos,1.0);
fragUV = vec2(uv.x, 1.0 - uv.y);
}
#version 410
uniform sampler2D greenMan;
in vec2 fragUV;
out vec4 outCol;
void main()
{
outCol=texture(greenMan,fragUV);
if (outCol.a < 1.0 )
{
discard;
}
}
- 运行结果
alpha测试和丢弃
- 使用纹理的alpha通道的第一种方法是进行alpha测试
- discard关键字可以丢弃片元
- 为alpha通道设置一个临界阈值,大于这个值的会正常渲染,小于的会丢弃,使用discard关键字实现。
- discard允许图形管线中删除片元,直接终止片元进入片元处理阶段
使用深度测试构建场景
- 我们将在外星人身后绘制一个森林背景纹理,在创建背景网格时,首先要注意Z坐标,在归一化设备坐标中,Z轴指向计算机屏幕,这意味着Z坐标较小的顶点将位于Z坐标较大的顶点之前。
- 由于我们仍使用规范化坐标指定所有顶点,这意味着我们创建的背景网格必须具有Z值大于0的顶点,不然程序将无法知道外星人应该渲染在背景前。
绘制背景
- ofEnableDepthTest():创建深度缓冲区,启动深度测试,这样即使先绘画外星人,后绘画背景,我们也能看见
- 深度缓冲区必须正常工作,因为外星人网格是在背景前渲染的,其次alpha测试着色器也必须正常工作,因为可以通过外星人四边形的不可见部分看到背景
#include "ofApp.h"
void buildMesh(ofMesh& mesh, float w, float h, glm::vec3 pos)
{
float verts[] = { -w + pos.x ,-h + pos.y,pos.z,
-w + pos.x,h + pos.y,pos.z,
w + pos.x,h + pos.y,pos.z,
w + pos.x,-h + pos.y,pos.z
};
float uvs[] = { 0,0, 0,1, 1,1, 1,0 };
for (int i = 0; i < 4; i++)
{
int idx = i * 3;
int uvIdx = i * 2;
mesh.addVertex(glm::vec3(verts[idx], verts[idx + 1], verts[idx + 2]));
mesh.addTexCoord(glm::vec2(uvs[uvIdx], uvs[uvIdx + 1]));
}
ofIndexType indices[6] = { 0,1,2,2,3,0 };
mesh.addIndices(indices, 6);
}
void ofApp::setup(){
ofDisableArbTex();
ofEnableDepthTest();
buildMesh(charMesh, 0.1, 0.2, glm::vec3(0.0, -0.25, 0.0));
buildMesh(backgroundMesh, 1.0, 1.0, glm::vec3(0.0, 0.0,0.5));
img.load("alien.png");
backgroundImg.load("forest.png");
shader.load("passthrough.vert", "alphaTest.frag");
}
void ofApp::draw(){
shader.begin();
shader.setUniformTexture("tex",img,0);
charMesh.draw();
shader.setUniformTexture("tex", backgroundImg, 0);
backgroundMesh.draw();
shader.end();
#version 410
layout (location = 0) in vec3 pos;
layout (location = 3) in vec2 uv;
out vec2 fragUV;
void main()
{
gl_Position = vec4(pos,1.0);
fragUV = vec2(uv.x, 1.0 - uv.y);
}
#version 410
uniform sampler2D tex;
in vec2 fragUV;
out vec4 outCol;
void main()
{
outCol=texture(tex,fragUV);
if (outCol.a < 1.0 )
{
discard;
}
}
- 运行结果
使用alpha混合创建云朵
- alpha不仅可以丢弃片元,还可以创建半透明对象
- 我们首先添加一个云朵的图像文件在bin/data中,声明一个云朵的网格变量与着色器与加载云朵对象
ofMesh charMesh;
ofMesh backgroundMesh;
ofMesh cloudMesh;
ofShader shader;
ofShader cloudShader;
ofImage img;
ofImage backgroundImg;
ofImage cloudImg;
void buildMesh(ofMesh& mesh, float w, float h, glm::vec3 pos)
{
float verts[] = { -w + pos.x ,-h + pos.y,pos.z,
-w + pos.x,h + pos.y,pos.z,
w + pos.x,h + pos.y,pos.z,
w + pos.x,-h + pos.y,pos.z
};
float uvs[] = { 0,0, 0,1, 1,1, 1,0 };
for (int i = 0; i < 4; i++)
{
int idx = i * 3;
int uvIdx = i * 2;
mesh.addVertex(glm::vec3(verts[idx], verts[idx + 1], verts[idx + 2]));
mesh.addTexCoord(glm::vec2(uvs[uvIdx], uvs[uvIdx + 1]));
}
ofIndexType indices[6] = { 0,1,2,2,3,0 };
mesh.addIndices(indices, 6);
}
void ofApp::setup(){
ofDisableArbTex();
ofEnableDepthTest();
buildMesh(charMesh, 0.1, 0.2, glm::vec3(0.0, -0.25, 0.0));
buildMesh(backgroundMesh, 1.0, 1.0, glm::vec3(0.0, 0.0,0.5));
buildMesh(cloudMesh, 0.25, 0.15, glm::vec3(-0.55, 0.0, 0.0));
img.load("alien.png");
backgroundImg.load("forest.png");
cloudImg.load("cloud.png");
shader.load("passthrough.vert", "alphaTest.frag");
cloudShader.load("passthrough.vert", "cloud.frag");
}
void ofApp::draw(){
shader.begin();
shader.setUniformTexture("tex",img,0);
charMesh.draw();
shader.setUniformTexture("tex", backgroundImg, 0);
backgroundMesh.draw();
shader.end();
cloudShader.begin();
cloudShader.setUniformTexture("tex", cloudImg, 0);
cloudMesh.draw();
cloudShader.end();
}
- 现在我们来单开一个云朵的着色器cloud.frag,我们希望该着色器能保留纹理的现有alpha,以免丢失云朵形状,但是我们要调整其他不透明片元上的alpha,所以我们需要使用条件测试
- cloud着色器 :
#version 410
uniform sampler2D tex;
in vec2 fragUV;
out vec4 outCol;
void main()
{
outCol = texture(tex,fragUV);
if (outCol.a > 0.8)
{
outCol.a = 0.8;
}
}
思考一下,在着色器里面通常应该避免分支语句出现
这个if语句会因为会使程序变慢到停止吗?
- 运行结果
GLSL的min()函数与max()函数
- min():返回最小值
- max():返回最大值
- 举例:
vec3 a = vec3(1,0,0);
vec3 b = vec3(0,1,1);
vec3 c = min(a,b);
vec3 d = max(a,b);
- 知道max与min函数后,我们来重写一下cloud着色器
- 重写cloud着色器
#version 410
uniform sampler2D tex;
in vec2 fragUV;
out vec4 outCol;
void main()
{
outCol = texture(tex,fragUV);
outCol.a = min(outCol.a,0.8);
}
此时片元的alpha值最高不会超过0.8,而alpha低于0.8的将保持不变
alpha混合的工作原理
- 简单来说,我们假设src和dst是两个颜色值,src是不透明片元,dst是透明片元,透过dst去看src,finalColor是src和dst的混合色,也就是说我们看一个透明的物体去看像不透明的物体,现在这个颜色会是一种混合值,也就是alpha混合
- 混合方程伪代码:
vec4 finalColor = src * src.a + (1.0 - src.a) * dst;
- 混合方程中的dst值是指混合发生时后置缓冲的当前值,src是指需要与当前存储在后置缓冲中的值混合的新片元,随着src片元的alpha值的增加,最终会得到来自src片元的颜色,而减少dst片元颜色
使用加法混合添加太阳光
vec 4 = finalColor = src + dst;
- 加法混合是将片元加在一起,与alpha混合不同,加法混合方程不使用片元的alpha通道来计算每一个片元要混合在一起的占比,它只是将每个片元100%相加
- 首先添加一个sun.png文件在bin/data中,声明一下太阳的网格对象与图像对象
ofMesh charMesh;
ofMesh backgroundMesh;
ofMesh cloudMesh;
ofMesh sunMesh;
ofShader shader;
ofShader cloudShader;
ofImage img;
ofImage backgroundImg;
ofImage cloudImg;
ofImage sunImg;
void buildMesh(ofMesh& mesh, float w, float h, glm::vec3 pos)
{
float verts[] = { -w + pos.x ,-h + pos.y,pos.z,
-w + pos.x,h + pos.y,pos.z,
w + pos.x,h + pos.y,pos.z,
w + pos.x,-h + pos.y,pos.z
};
float uvs[] = { 0,0, 0,1, 1,1, 1,0 };
for (int i = 0; i < 4; i++)
{
int idx = i * 3;
int uvIdx = i * 2;
mesh.addVertex(glm::vec3(verts[idx], verts[idx + 1], verts[idx + 2]));
mesh.addTexCoord(glm::vec2(uvs[uvIdx], uvs[uvIdx + 1]));
}
ofIndexType indices[6] = { 0,1,2,2,3,0 };
mesh.addIndices(indices, 6);
}
void ofApp::setup(){
ofDisableArbTex();
ofEnableDepthTest();
buildMesh(charMesh, 0.1, 0.2, glm::vec3(0.0, -0.25, 0.0));
buildMesh(backgroundMesh, 1.0, 1.0, glm::vec3(0.0, 0.0,0.5));
buildMesh(cloudMesh, 0.25, 0.15, glm::vec3(-0.55, 0.0, 0.0));
buildMesh(sunMesh, 1.0, 1.0, glm::vec3(0.0, 0.0, 0.4));
img.load("alien.png");
backgroundImg.load("forest.png");
cloudImg.load("cloud.png");
sunImg.load("sun.png");
shader.load("passthrough.vert", "alphaTest.frag");
cloudShader.load("passthrough.vert", "cloud.frag");
}
- 因为openFrameworks会默认情况下使用alpha混合,而现在我们需要使用alpha混合功能,所以现在需要添加几个函数。
- ofDisableBlendMode():禁用混合模式
- ofEnableBlendMode(ofBlendMode::OF_BLENDMODE_ALPHA):使用alpha混合
- ofEnableBlendMode(ofBlendMode::OF_BLENDMODE_ADD):使用加法混合
void ofApp::draw(){
ofDisableBlendMode();
shader.begin();
shader.setUniformTexture("tex",img,0);
charMesh.draw();
shader.setUniformTexture("tex", backgroundImg, 0);
backgroundMesh.draw();
shader.end();
ofEnableBlendMode(ofBlendMode::OF_BLENDMODE_ALPHA);
cloudShader.begin();
cloudShader.setUniformTexture("tex", cloudImg, 0);
cloudMesh.draw();
ofEnableBlendMode(ofBlendMode::OF_BLENDMODE_ADD);
cloudShader.setUniformTexture("tex", sunImg, 0);
sunMesh.draw();
cloudShader.end();
}
- 解释一下为什么在绘画外星人与背景时要禁用一下混合,因为外星人和背景要么是完全透明要么完全不透明,这意味着alpha混合不需要执行然后操作去渲染它们
- 运行结果
- 这个云朵不太准确,是因为使用了alpha混合隐藏四边形,仍然在提交整个云朵四边形的深度信息
- 解决方法:在绘画外星人和背景前启用深度测试,在画云太阳时禁用深度测试,也就是在开始为半透明网格发出绘制调用之前禁止深度测试,然后在不透明网格之前再次启动深度测试
void ofApp::draw(){
ofDisableBlendMode();
ofEnableDepthTest();
shader.begin();
shader.setUniformTexture("tex",img,0);
charMesh.draw();
shader.setUniformTexture("tex", backgroundImg, 0);
backgroundMesh.draw();
shader.end();
ofDisableDepthTest();
ofEnableBlendMode(ofBlendMode::OF_BLENDMODE_ALPHA);
cloudShader.begin();
cloudShader.setUniformTexture("tex", cloudImg, 0);
cloudMesh.draw();
ofEnableBlendMode(ofBlendMode::OF_BLENDMODE_ADD);
cloudShader.setUniformTexture("tex", sunImg, 0);
sunMesh.draw();
cloudShader.end();
}
- 运行结果
- 在某些引擎中,启动深度写入和深度测试之间存在差异,引擎允许你告诉GPU使用现有的深度缓冲区,允许网格被其它网格遮挡,但是不能写入网格本身深度信息
使用精灵表制作动画
- 就是贴图,疯狂贴图,一帧一帧的贴,添加一张外星人的精灵表
- 单个精灵的大小是(0.28,0.19),首先就是将UV坐标乘以该尺寸大小向量,这会得到一个UV矩形
- 对这个UV进行缩放后,添加一个统一向量让UV矩形移动起来
- 传入一个统一vec2设置图片大小,传入另一个vec2来作为帧,将这些值相乘,获取需要应用到顶点UV坐标的平移向量
- 声明加载精灵表的对象,创建纹理
- 新建一个move.vert顶点坐标器
- 然后为顶点着色器的统一变量生成值
void buildMesh(ofMesh& mesh, float w, float h, glm::vec3 pos)
{
float verts[] = { -w + pos.x ,-h + pos.y,pos.z,
-w + pos.x,h + pos.y,pos.z,
w + pos.x,h + pos.y,pos.z,
w + pos.x,-h + pos.y,pos.z
};
float uvs[] = { 0,0, 0,1, 1,1, 1,0 };
for (int i = 0; i < 4; i++)
{
int idx = i * 3;
int uvIdx = i * 2;
mesh.addVertex(glm::vec3(verts[idx], verts[idx + 1], verts[idx + 2]));
mesh.addTexCoord(glm::vec2(uvs[uvIdx], uvs[uvIdx + 1]));
}
ofIndexType indices[6] = { 0,1,2,2,3,0 };
mesh.addIndices(indices, 6);
}
void ofApp::setup(){
ofDisableArbTex();
ofEnableDepthTest();
buildMesh(charMesh, 0.1, 0.2, glm::vec3(0.0, -0.25, 0.0));
buildMesh(backgroundMesh, 1.0, 1.0, glm::vec3(0.0, 0.0,0.5));
buildMesh(cloudMesh, 0.25, 0.15, glm::vec3(-0.55, 0.0, 0.0));
buildMesh(sunMesh, 1.0, 1.0, glm::vec3(0.0, 0.0, 0.4));
img.load("walk_sheet.png");
backgroundImg.load("forest.png");
cloudImg.load("cloud.png");
sunImg.load("sun.png");
shader.load("passthrough.vert", "alphaTest.frag");
cloudShader.load("passthrough.vert", "cloud.frag");
moveShader.load("move.vert", "alphaTest.frag");
}
操纵UV坐标以使用精灵表的顶点着色器
#version 410
layout (location = 0) in vec3 pos;
layout (location = 3) in vec2 uv;
uniform vec2 size;
uniform vec2 offset;
out vec2 fragUV;
void main()
{
gl_Position = vec4(pos,1.0);
fragUV = vec2(uv.x, 1 - uv.y) * size + (size * offset);
}
draw函数为顶点着色器的统一变量生成值
void ofApp::draw(){
static float frame = 0.0;
frame = (frame > 10) ? 0.0 : frame += 0.2;
glm::vec2 Size = glm::vec2(0.28, 0.19);
glm::vec2 Frame = glm::vec2((int)frame % 3, (int)frame / 3);
ofDisableBlendMode();
ofEnableDepthTest();
moveShader.begin();
moveShader.setUniform2f("size", Size);
moveShader.setUniform2f("offset", Frame);
moveShader.setUniformTexture("tex", img, 0);
charMesh.draw();
moveShader.end();
shader.begin();
shader.setUniformTexture("tex", backgroundImg, 0);
backgroundMesh.draw();
shader.end();
ofDisableDepthTest();
ofEnableBlendMode(ofBlendMode::OF_BLENDMODE_ALPHA);
cloudShader.begin();
cloudShader.setUniformTexture("tex", cloudImg, 0);
cloudMesh.draw();
ofEnableBlendMode(ofBlendMode::OF_BLENDMODE_ADD);
cloudShader.setUniformTexture("tex", sunImg, 0);
sunMesh.draw();
cloudShader.end();
}
- 运行结果