[GEiv]第七章:着色器 高效的GPU渲染方案

第七章:着色器

高效的GPU渲染方案

本章介绍着色器的基本知识以及Geiv下对其提供的支持接口,并以“渐变高斯模糊”为线索进行实例的演示讲解。

[背景信息]

[计算机中央处理器的局限性]

在大学的“数字图像处理”课程中,老师讲解了高斯模糊的基本算法,并使用C#进行了基本实现。高斯模糊,简单地说,就是使用高斯权重模板对图像的每一个像素进行再计算、填充,以达到模糊的效果。

在课程中,对于给定的模板与模糊度系数,对一副800X600的图像进行模糊处理,需要计算48万个像素点,尽管当时机房已经普及了酷睿系列CPU,但这个过程依旧耗时2~3秒。

于是有这样的一个问题:在游戏中,我们经常看到画面进行模糊到清晰的动态转变,即使是次世代的GBA\SFC等游戏主机依然有这样的效果实现,而论硬件性能,我们的PC应该远远高于这些次世代游戏机,而2、3秒的运算结果已经明确告诉我,想要进行更为高效的模糊计算,显示地挨个计算像素点是不可能达到预期效率的,想要一秒钟完成60幅不同模糊度的图像计算,还要另寻他路。

为什么计算性能普遍较低的游戏机却能实现高性能CPU都无法流畅做到的运算呢?笔者认为还是因为CPU的结构差异导致的,PC的任务种类多,过程复杂,需要复杂的指令集系统;但游戏机的CPU主要为图像而生,只需使用针对图像的少数精简指令系统即可。

总而言之,为了保证CPU在复杂任务下的通用性,计算机的CPU并不擅长进行图形运算,并且在一些场合下,图形运算的效率相当低下。

[图形处理器GPU]

为了弥补CPU处理图形的缺陷,GPU便诞生了,GPU是针对图形运算而生的处理器,如今的主流游戏PC都会有一个高性能的GPU。具体的概念笔者就不在这里提了,相信搞计算机的都会对其了解一二。

[在编程中使用GPU运算资源]

在大多数开发场合下,无论是C还是Java,我们敲的代码都无疑地运行在CPU之上,那我们怎样才能显示地调用GPU的资源呢?在OPENGL中,除了常用的固定管线式编程外,还提供了更为灵活的功能,它允许开发者使用着色语言(GLSL,一种类C语言)编写名为“着色器”的程序,经编译运行于GPU之上并接管一部分GPU内置功能。

[Opengl着色器简述]

介于笔者履历有限,这里只做简单介绍,详细概念可以参考《Opengl着色语言》一书,它对着色器和GLSL有着非常详细的介绍。

一个Opengl着色器(下简称着色器)由一个顶点着色器与片段着色器构成,它们负责的工作大部分是不同的,仅有少部分交集。

Opengl为着色器的校验、编译、连接和设置参数提供了完整的API,只要显卡支持,在Opengl上下文中即可由源文件得到着色器程序,无需其他环境。

编写着色器的目的,是为了实现诸如模糊、雾化、发光等固定管线不易叙述的功能,进而丰富图像的表现能力。很多游戏引擎将Opengl中与着色器相关的API包装到了上层,我在设计时也采用了这种方案。

[内置着色器]

在GEiv中,内置了5个系统着色器,它们是在设计之初进行着色器测试时所保留下来的,并不是由于“常用”(笔者感觉着色器的编写往往很有针对性,一般很难有常用一说)。

依次介绍:

SD_ANTICOLOR:将目标色改为反色

SD_EMBOSS_MODE9:浮雕效果,后面的MODE9表示它使用的是3X3的模板,可以适应一般的需求,若要提高精确度,可以将模板扩充至4X4\5X5等。

SD_GAUSSIAN_MODE9:高斯模糊

SD_LAPLACIAN_MODE9:锐化

SD_MEAN_MODE9:均值化

[为图元设置着色器]

在GEiv中,使用着色器的对象是图元,使用其setShaderProgram方法将着色器绑顶到对应图元上,如:

Obj T =UES.creatObj(UESI.BGIndex);
T.addGLImage(0,0, "./mdls.jpg");
T.setShaderProgram(SysShader.SD_GAUSSIAN_MODE9);//将系统着色器绑定到图元T上

[设置Uniform参数]

Uniform修饰词(不是数据类型)指明一个GLSL中的变量是外部传入的,并且在整个着色器执行过程中不会改变(区别于attribute),例如对于实现模糊变化来讲,“模糊度”这个变量属于Uniform型无疑。

在GLSL中,数组有很多存在形式,float[]可以表示一维线性浮点数组,vect2则表示了一个点对,而vec2[]则表示了一维线性浮点“数对”组,类似的还有vect3\4等等这样在java中没有现成的对应结构的数据,也就是说,设置Uniform参数时必须给定我们的数据源(一维线性数组)和映射到GLSL中变量的方式。

在GEiv的图元类中,setShaderUniform(String uniformName, Object value, intTPFLAG)方法可以设置该图元上绑定的着色器参数,这个方法的参数依次是:

uniformName是着色器中对于的变量名称。

value是数据源,目前只支持float[]、Float[]或单个的Float型。

TPFLAG 是指定的映射方式,你可以直接从ShaderController的静态值部分直接引用。它可以是下列值之一:

AFLOAT与FLOATS分别对应单个float值与一维float数组。

VERTXS表示由X个点对组成的数组,即该数组每个元素包含一个X维度向量。所给数据源必须是一个X整数倍大小的一维线性数组,该数组会按照顺序依次填充这些X维度向量,例如使用TP_VERT2S时,数据源一维数组的前两个元素构成了着色器向量数组的第一个向量元素。

VERTEX表示一个X维度向量。要求给定的一维数据源数组大小必须为X。

[自定义用户着色器]

除了使用SysShader中现成的着色器外,还可以使用用户自定义的着色器程序。

在引擎的句柄UESI中,包装并简化的Opengl产生着色器的API:

UESI.createShaderProgram(String spName,String vpPath, String fpPath);

参数介绍:

spName是用户为着色器起的名字。

vpPath是顶点着色器的源文件路径,如”./vp.txt”。

fpPath是片段着色器源文件路径。

之后我们只需要使用设置着色器的API对图元绑定spName就行了,与之前介绍的字库相同,spName也具有全局效应,可以为其它上下文的图元绑定。

[实例-渐变高斯模糊]

您可以在[GitHub]Sample\Sample-Shader下找到这个例子及使用的资源。

Main.java

package com.geiv.test;

import geivcore.R;
import geivcore.UESI;

public class Main{
	public static void main(String[] args) {
		UESI UES = new R();
		new Guassion(UES);
	}
}

Guassion.java

package com.geiv.test;

import geivcore.SerialTask;
import geivcore.UESI;
import geivcore.engineSys.shadercontroller.ShaderController;
import geivcore.engineSys.shadercontroller.SysShader;
import geivcore.enginedata.canonical.CANExPos;
import geivcore.enginedata.obj.Obj;

import java.awt.event.KeyEvent;
import java.util.Arrays;

public class Guassion implements SerialTask{
	UESI UES;
	float[] g_aryVerticalOffset;
	float[] vertstatic;
	float bur = 600f;//bur作为模糊度因子,使用静态偏移量(vertstatic)除以模糊度因子得到不同的偏移量参数(g_aryVerticalOffset),由于GLSL上下文是使用的OPENGL坐标式,与GEiv有所不同,因此初始因子经计算转换后定为600f

	Obj T;
	public Guassion(UESI UES) {
		this.UES = UES;

		T = UES.creatObj(UESI.BGIndex);
		T.addGLImage(0, 0, "./mdls.jpg");

		T.setShaderProgram(SysShader.SD_GAUSSIAN_MODE9);//设置着色器
		T.setShaderUniform(SysShader.NA_WEIGHTARGS,SysShader.BR_GAUSSIAN_MODE9, ShaderController.TP_FLOATS);//设置Unfiorm参数,其中规格化后的权重模板及其在GLSL上下文中的名称已经给定,它们可以由SysShader直接引用。

		vertstatic = new float[]{-1,-1, 0,-1, 1,-1
				  -1, 0, 0, 0, 1, 0
				  -1, 1, 0, 1, 1, 1};//偏移量模板,看不懂的话可以参考下面的图

		g_aryVerticalOffset = Arrays.copyOf(vertstatic, vertstatic.length);

		for(int i = 0;i < g_aryVerticalOffset.length;i++)
		{
			g_aryVerticalOffset[i] = vertstatic[i]/bur;
		}

		T.setShaderUniform(SysShader.NA_OFFSETARGS, g_aryVerticalOffset,ShaderController.TP_VERT2S);//设置Uniform偏移量
		T.setPosition(CANExPos.POS_CENTER);
		T.show();

		UES.addSerialTask(this);
	}

	@Override
	public void Serial(int clock) {//挂载一个扫描式的键盘输入,当Z键按下时bur增加,重置偏移量并重设Uniform。当X键按下时则相反。
		if(UES.getKeyStatus(KeyEvent.VK_Z))
		{
			T.setShaderUniform(SysShader.NA_OFFSETARGS,g_aryVerticalOffset,ShaderController.TP_VERT2S);
			bur+=4f;
			for(int i = 0;i < g_aryVerticalOffset.length;i++)
			{
				g_aryVerticalOffset[i] = vertstatic[i]/bur;
			}
		}
		else if(UES.getKeyStatus(KeyEvent.VK_X))
		{
			T.setShaderUniform(SysShader.NA_OFFSETARGS,g_aryVerticalOffset,ShaderController.TP_VERT2S);
			bur-=4f;
			for(int i = 0;i < g_aryVerticalOffset.length;i++)
			{
				g_aryVerticalOffset[i] = vertstatic[i]/bur;
			}
		}
	}
}

关于偏移量模板的映射,实际上是这样的↓:

除此之外,还有高斯模糊着色器的两部分源代码

顶点着色器:

attribute float sys_pIndex;
void main(void)
{
	gl_TexCoord[0] = gl_MultiTexCoord0;
	gl_Position = ftransform();
}

↑这个顶点着色器没有写任何实质性的功能,它被很多片段着色器共用。

高斯-片段着色器:

const int g_iWeightNumber = 9;//权重模板大小
uniform sampler2D g_FilterTexture;//我们的纹理
uniform float g_aryWeight[g_iWeightNumber];//权重数组
uniform vec2 g_aryOffset[g_iWeightNumber];//偏移量-即使使用9个格子,也没有规定必须是相邻的格子;偏移量叙述着权重模板格子间的实际像素距离,取值越高,模糊度越高。
void main(void)
{
	vec4 vec4Sum = vec4(0.0);
	for(int i = 0; i < g_iWeightNumber; ++i)
	{
		vec4Sum += texture2D(g_FilterTexture, gl_TexCoord[0].st + g_aryOffset[i])*g_aryWeight[i];//这里进行实质的计算过程,也就是替换原先我们让CPU挨个计算像素点的部分。gl_TexCoord[0]是绑定的0号纹理(Opengl中图形是可以绑定多个纹理的),它的st就是位置,一个vec2类型量;位置与偏移量进行加和,得到偏移位置。之后根据纹理和偏移位置取出对应的rgba四维向量,它是一个vec4类型。进一步,将我们的vec4乘以规格化后的权重,并加和到vec4Sum上,由于是规格化后的权重,因此RGBA都不会越界。
	}
	gl_FragColor = vec4Sum;// 最后,我们将这个计算完毕的RGBA结果填入纹理中。
}

最后,执行结果(按住X键时):

↑可以观察到由清晰到模糊的动态过程。

[总结]

本章介绍了着色器的使用场合以及GEiv下对应的API。

使用着色器是为了实现固定管线难以叙述的效果。

着色器运行于GPU,在对图像渲染时效率比直接使用CPU运算高出很多。

时间: 2024-09-29 20:07:54

[GEiv]第七章:着色器 高效的GPU渲染方案的相关文章

[GEiv]第七章:着色器 高效GPU渲染方案

第七章:着色器 高效GPU渲染方案 本章介绍着色器的基本知识以及Geiv下对其提供的支持接口.并以"渐变高斯模糊"为线索进行实例的演示解说. [背景信息] [计算机中央处理器的局限性] 在大学的"数字图像处理"课程中,老师解说了高斯模糊的基本算法.并使用C#进行了基本实现.高斯模糊.简单地说,就是使用高斯权重模板对图像的每个像素进行再计算.填充,以达到模糊的效果. 在课程中.对于给定的模板与模糊度系数,对一副800X600的图像进行模糊处理.须要计算48万个像素点,

【OPENGL】第三章 着色器基础(一)

在这一章,我们会学习什么是着色器(Shader),什么是着色器语言(OpenGL Shading Language-GLSL),以及着色器怎么和OpenGL程序交互. 首先我们先来看看什么叫着色器. Shader(着色器)是用来实现图像渲染的,用来替代固定渲染管线的可编程程序. 着色器替代了传统的固定渲染管线,可以实现3D图形学计算中的相关计算,由于其可编程性,可以实现各种各样的图像效果而不用受显卡的固定渲染管线限制.这极大的提高了图像的画质. 在上一篇文章( http://www.cnblog

第4章:缓冲区、着色器、GLSL

原文链接: http://www.rastertek.com/gl40tut04.html Tutorial 4: Buffers, Shaders, and GLSL This tutorial will be the introduction to writing vertex and pixel shaders in OpenGL 4.0. It will also be the introduction to using vertex and index buffers in OpenG

顶点着色器和片断着色器

顶点和片段着色器 必备知识 熟悉Stage3D API.最好之前使用过VertexBuffer.在继续这个教程之前一定要先阅读本系列的第一个教程(Stage3D原理).    所需软件 Flash Builder 4.5 Premium (Download trial) Flash Professional CS5.5 (Download trial) 在本章中,你将对着色器(Shaders)有一个大概的了解.着色器是Stage3D渲染管道的核心.你将学到顶点和片段着色器是个什么东东,它们在3D

OpenGL管线(用经典管线代说着色器内部)

图形管线(graphics pipeline)向来以复杂为特点,这归结为图形任务的复杂性和挑战性.OpenGL作为图形硬件标准,是最通用的图形管线版本.本文用自顶向下的思路来简单总结OpenGL图形管线,即从最高层开始,然后逐步细化到管线图中的每个框,再进一步细化到OpenGL具体函数.注意,这里用经典管线代说着色器内部,也就是OpenGL固定管线功能(Fixed-Function,相对于programmable也即可编程着色器),也会涉及着色器,但差不多仅限于“这些固定管线功能对应xx着色器”

【浅墨Unity3D Shader编程】之三 光之城堡篇:子着色器、通道与标签的写法 &amp; 纹理混合

本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/41175585 作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442 邮箱: [email protected] 本文介绍了Unity中子着色器.通道和标签相关的详细概念与写法,以及纹理的设置方法,基本的纹理混合写法,写了5个Shader作为本文Shader讲解的实战内容,最后创建了一个梦幻的光之

Directx 中HLSL高级着色器语言 脑补一下吧

HLSL初级教程 作者:trcj 目录 前言 1.HLSL入门 1.1什么是着色器 1.2什么是HLSL 1.3怎么写HLSL着色器 1.4怎么用HLSL着色器 2.顶点着色器 2.1可编程数据流模型 2.2顶点声明 2.3用顶点着色器实现渐变动画 3.像素着色器 3.1多纹理化 3.2多纹理效果的像素着色器 3.3应用程序 4.HLSL Effect(效果框架) 4.1Effect代码结构 4.2用Effect实现多纹理化效果 结语 参考资料 前言 本教程针对HLSL(High Level S

Unity3d之Shader编程:子着色器、通道与标签的写法 &amp; 纹理混合

一.子着色器 Unity中的每一个着色器都包含一个subshader的列表,当Unity需要显示一个网格时,它能发现使用的着色器,并提取第一个能运行在当前用户的显示卡上的子着色器. 我们知道,子着色器定义了一个渲染通道的列表,并可选是否为所有通道初始化所需要的通用状态.子着色器的写法如下: Subshader{ [Tags] [CommonState] Passdef [Passdef ...] } 也就是通过可选标签,通用状态 和 一个Pass 定义的列表构成了子着色器. 当Unity选择用于

[GLSL]着色器周记01!

我决定开个新坑了.以后每周五更新.这是GLSL的学习周记!GLSL就是OPENGL SHADER LANGUAGE的简称,就是着色器语言.着色器是一种交给显卡运行的小程序,这种小程序可以用GLSL来写,写好后交给OPENGL编译,就可以在显卡上运行了. 那么问题来了!为什么要给显卡运行呢?显卡是一种特殊的处理器,有核心,有寄存器,还有内存,不过对比CPU,最大的特点就是显卡的核心更多.多多少呢?一般CPU有4-8个核心,而显卡则是100个左右的核心!不过由于造价还有空间的限制,显卡的某些功能会被