shader实战开发 04半透明与深度

半透明与深度前言

  • 计算机图形学中通常由红色、绿色、蓝色和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();
	//调整角色网格位置,使其宽度是屏幕的1/4,高度是屏幕的1/2
	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;//定义一个接收采样器与UV坐标的对象

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;//定义一个接收采样器与UV坐标的对象

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);
//此时c=vec3(0,0,0)
vec3 d = max(a,b);
//此时d=vec(1,1,1)
  • 知道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混合的工作原理

  • 简单来说,我们假设srcdst是两个颜色值,src是不透明片元,dst是透明片元,透过dst去看srcfinalColorsrcdst的混合色,也就是说我们看一个透明的物体去看像不透明的物体,现在这个颜色会是一种混合值,也就是alpha混合
  • 混合方程伪代码:
vec4 finalColor = src * src.a + (1.0 - src.a) * dst;
  • 混合方程中的dst值是指混合发生时后置缓冲的当前值,src是指需要与当前存储在后置缓冲中的值混合的新片元,随着src片元的alpha值的增加,最终会得到来自src片元的颜色,而减少dst片元颜色

使用加法混合添加太阳光

  • 加法混合(additive blending)
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);//使用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);//使用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",img,0);
	//charMesh.draw();
	
	//绘画背景
	shader.setUniformTexture("tex", backgroundImg, 0);
	backgroundMesh.draw();

	shader.end();

	ofDisableDepthTest();//禁用深度测试
	ofEnableBlendMode(ofBlendMode::OF_BLENDMODE_ALPHA);//使用alpha混合

	cloudShader.begin();
	//绘画云朵
	cloudShader.setUniformTexture("tex", cloudImg, 0);
	cloudMesh.draw();

	ofEnableBlendMode(ofBlendMode::OF_BLENDMODE_ADD);//使用加法混合

	//绘画太阳
	cloudShader.setUniformTexture("tex", sunImg, 0);
	sunMesh.draw();

	cloudShader.end();
}
  • 运行结果
    请添加图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值