首页 > 其他分享 >04 高级OpenGL

04 高级OpenGL

时间:2023-05-14 22:11:39浏览次数:27  
标签:glm 04 OpenGL 高级 TEXTURE shader 测试 深度 GL

一、深度测试

1. 深度缓冲

  • 深度缓冲记录了屏幕空间内的像素深度(width*height),它会以16、24或32位float的形式储存它的深度值。在大部分的系统中,深度缓冲的精度都是24位的。

2. gl_FragCoord

  • 对于每一个片段的深度,可以从GL_FragCoord中获取,其xy坐标是屏幕空间的坐标,其z坐标是0到1的深度值。
  • GL_FragCoord是这样得到的:
    • 首先经过MV变换,得到观察空间的坐标
    • 然后经过投影变换和正交变换,得到裁剪空间,其中在进行投影变换时深度值由线性深度变成了非线性深度,给近处的深度了更明显的区分度
    • 再进行除以w的操作,得到[-1,1]^3的NDC标准化设备空间
    • 然后将[-1,1]3的NDC标准化设备空间变换到[0,1]3,注意此时z向已经是取反的了,即左手系(该操作一般在得到NDC时完成,即NDC是左手系),此时,z值已经变成0-1的非线性深度了。
    • 然后,按照glViewPort的宽度和高度,将xy坐标调整到[0,width]x[0,height]。具体着色片段的坐标可能是整数值,也可能是将像素边界取整数值而像素坐标取中值,一般是后者,那么width就会对应width-1个像素,高度方向亦然。
    • 然后进行光栅化,可以将顶点坐标的深度插值得到片段深度
    • 这便是gl_FragCoord的坐标。

3. 提前深度测试

  • 深度测试一般是在片段着色器完成颜色计算后,再进行alpha测试、模板测试、深度测试,来决定是否将其加载到颜色缓冲中。可见,所有片段都需要计算颜色,无论是否会被显示,这是一个很大的开销,因为片段着色器是shader的一个主要计算部分。
  • 因此,可以采取提前深度测试,即在片段着色器之前进行深度比较,因为经过坐标变换之后就得到了顶点的屏幕空间坐标和深度,再光栅化插值之后就可以得到片段深度了,因此片段深度是在片段着色器之前得到了,所以这是可行的,如果不通过深度测试就不进行着色了。
  • 但是,很多情况下提前深度测试就失效了,因为提前深度测试只考虑了遮蔽问题,而缺少与其他问题的方案的配合,尤其是当深度测试结果不能完全决定着色时,直接通过提前深度测试就丢弃片段就会造成坏的效果。
  • 另外,提前深度测试的效果取决于片段的绘制顺序,当由浅及深绘制时,效果最好,不存在重复绘制的情况,反之就没有效果。

4. OpenGL的深度测试

  • glEnable(GL_DEPTH_TEST);
    开启深度测试
  • glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    每帧开始需要清理深度缓冲,就如同颜色缓冲的清理。
  • glDepthMask(GL_FALSE);
    开启深度缓冲只读不写,即只测试,不重写。
  • glDepthFunc(GL_LESS);
    深度测试的通过方案:

5. 线性深度

  • 保存在gl_FragCoord中的深度是非线性的深度,如果有必要转换成线性深度只需要进行反变换就可以了:首先将0-1反变换为-1到1,得到裁剪空间的深度,然后反变换坐标变换就可以得到观察空间的深度,即线性深度。
  • 注意,下面的变换没有包含左手系向右手系的转换,所以越远深度值越大。
float LinearizeDepth(float depth) 
{
    float z = depth * 2.0 - 1.0; // back to NDC 
    return (2.0 * near * far) / (far + near - z * (far - near));    
}

二、模板测试

1.模板缓冲

  • 模板缓冲是8位的,有256个值,可以由GLFW实现。

2.OpenGL的模板缓冲操作

  • glEnable(GL_STENCIL_TEST)
    开启模板测试操作
  • glClear(GL_STENCIL_BUFFER_BIT);
    清理缓冲,置为0.
  • glStencilMask(0xFF) glStencilMask(0x00)
    写入缓冲时对被写入的值按位与操作,因此可见是八位的值,0xFF为原样写入,0x00为禁止写入(原文说这里是禁止写入,但是我猜测可能是写入0x00,由于缓冲默认是被清理为全零值,所以写入零就是禁止写入)。
  • glStencilFunc(GLenum func, GLint ref, GLuint mask)
    该函数定义了测试方式,包括比较对象、比较内容和通过条件
    • 第一个参数用于设置通过条件,指将缓冲中的值与参考值对比:GL_NEVER、GL_LESS、GL_LEQUAL、GL_GREATER、GL_GEQUAL、GL_EQUAL、GL_NOTEQUAL和GL_ALWAYS
    • 第二个参数用于设置参考值
    • 第三个参数用于将参考值和缓冲值在比较前都进行按位与运算,即用于实现按位提取的比较操作。如果没有特殊要求就设置0x00。
glStencilFunc(GL_EQUAL, 1, 0xFF);
  • glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)
    • 该函数用于定义测试后的操作,指对缓冲的写操作。第一个参数用于设置模板测试失败后的操作,第二个是模板通过但是深度失败的操作,第三个是都通过的操作。默认三个参数都是keep,即不修改缓冲。
    • 从这三个选项中可以看出,模板操作是在深度测试之前的,这样做的原因是:虽然二者都是片段级别的测试,但是深度测试是用于渲染的,而模板操作是用于实现效果的,所以后者的抽象级别要更高,因此如果模板测试不通过,即没有相关的效果绘制的需求,但是仍然实现了深度测试并更新了缓冲,那么么此时的缓冲就被破坏了。

3. 应用

//VAO
	glBindVertexArray(stencilVAO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 11 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 11 * sizeof(float), (void*)(6 * sizeof(float)));
	glEnableVertexAttribArray(1);
//shader
	Shader twocubeShader("res/shader/stencilVertex.shader", "res/shader/stencilFragment.shader");
	Shader singlecolorShader("res/shader/stencilVertex.shader", "res/shader/singlecolorFragment.shader");
		//stencil
		glEnable(GL_STENCIL_TEST);
		glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);

		twocubeShader.use();

		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, texture1);
		twocubeShader.setInt("texture_diffuse", 0);

		glBindVertexArray(stencilVAO);

		glStencilMask(0x00);

		twocubeShader.setMat4("view", glm::value_ptr(view));
		twocubeShader.setMat4("projection", glm::value_ptr(projection));
		glm::mat4 cubemodel3;
		cubemodel3 = glm::translate(cubemodel3, glm::vec3(11.0, -16.0, 11.0));
		cubemodel3 = glm::scale(cubemodel3, glm::vec3(10.0));
		twocubeShader.setMat4("model", glm::value_ptr(cubemodel3));
		glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);

		glStencilFunc(GL_ALWAYS, 1, 0xFF);
		glStencilMask(0xFF);

		glm::mat4 cubemodel1;
		cubemodel1 = glm::translate(cubemodel1, glm::vec3(10.0, -10.0, 10.0));
		twocubeShader.setMat4("model", glm::value_ptr(cubemodel1));
		glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
		glm::mat4 cubemodel2;
		cubemodel2 = glm::translate(cubemodel2, glm::vec3(11.0, -10.0, 11.0));
		twocubeShader.setMat4("model", glm::value_ptr(cubemodel2));
		glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);


		singlecolorShader.use();

		glBindVertexArray(stencilVAO);

		glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
		glStencilMask(0x00);
		glDisable(GL_DEPTH_TEST);

		singlecolorShader.setMat4("view", glm::value_ptr(view));
		singlecolorShader.setMat4("projection", glm::value_ptr(projection));
		glm::mat4 colormodel1;
		colormodel1 = glm::scale(cubemodel1, glm::vec3(1.1));
		singlecolorShader.setMat4("model", glm::value_ptr(colormodel1));
		glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
		glm::mat4 colormodel2;
		colormodel2 = glm::scale(cubemodel2, glm::vec3(1.1));
		singlecolorShader.setMat4("model", glm::value_ptr(colormodel2));
		glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);

		glStencilMask(0xFF);
		glEnable(GL_DEPTH_TEST);

三、混合

1. 丢弃片段

  • 丢弃片段只需要在片段着色器中检查纹理颜色的alpha值,然后通过设置阈值,进行discard就可以了。
  • 需要注意的是,当进行discard时,如果采取纹理环绕GL_REPEAT,那么超过纹理坐标范围内的片段会显示出一条边框,因此采用GL_CLAMP_TO_EDGE不做环绕。会出现这种情况是因为,光栅化时会进行反走样,那么边缘的片段就会超出三角形范围。
#version 330 core

layout(location = 0) in vec3 aPos;
layout(location = 1) in vec2 aTexCoord;

out vec2 texCoord;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
	gl_Position = projection * view * model * vec4(aPos, 1.0);
	texCoord = aTexCoord;
}
//VAO
	unsigned int grassVAO;
	glGenVertexArrays(1, &grassVAO);
	glBindVertexArray(grassVAO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 11 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 11 * sizeof(float), (void*)(6 * sizeof(float)));
	glEnableVertexAttribArray(1);
//program
Shader grassShader("res/shader/discardVertex.shader", "res/shader/discardFragment.shader");
//texture
	unsigned int texture3;
	glGenTextures(1, &texture3);
	glBindTexture(GL_TEXTURE_2D, texture3);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

	stbi_set_flip_vertically_on_load(true);
	data = stbi_load("res/texture/grass.png", &width, &height, &nrChannels, 0);
	if (data)
	{
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
		glGenerateMipmap(GL_TEXTURE_2D);
	}
	else
	{
		std::cout << "Failed to load texture3." << std::endl;
	}
	stbi_image_free(data);
//grass
	vector<glm::vec3> grassPositions;
	grassPositions.push_back(glm::vec3(10.0, -10.0, 6.0));
	grassPositions.push_back(glm::vec3(9.0, -10.0, 8.0));
	grassPositions.push_back(glm::vec3(8.0, -10.0, 7.0));

//discard
		glDisable(GL_STENCIL_TEST);
		grassShader.use();

		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D,texture3);
		grassShader.setInt("texture_grass", 0);

		grassShader.setMat4("view", glm::value_ptr(view));
		grassShader.setMat4("projection", glm::value_ptr(projection));
		for (unsigned int i = 0; i != grassPositions.size(); i++)
		{
			glm::mat4 model;
			model = glm::translate(model, grassPositions[i]);
			grassShader.setMat4("model", glm::value_ptr(model));
			glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
		}

2. 混合

2.1 OpenGL的混合操作

  • gllEnable(GL_BLEND);
    启动混合。
  • glBlendFunc(GLenum sfactor, GLenum dfactor)
    设置源片段与目标片段颜色的因子:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  • glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
    分别设置RGB颜色通道(前)与alpha通道(后)的因子。
  • glBlendEquation(GLenum mode)
    设置运算方式。默认加法。

2.2 应用

  • 只需要设置glEnable和因子就可以使用了。
  • 需要处理好blend与深度测试的关系,blend是在深度测试后面进行的,因此如果半透明物体在被其遮挡的物体之前绘制,那么在绘制被遮挡物体时不会着色,因此就不会显示透明效果。为此,应当按照这样的顺序渲染:首先是所有不透明物体,然后将透明物体的远近进行排序,然后由远及近的渲染。这样一来,前面的透明物体就可以从颜色缓冲中拿到目标颜色,并进行blend。
//VAO
	unsigned int winVAO;
	glGenVertexArrays(1, &winVAO);
	glBindVertexArray(winVAO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 11 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 11 * sizeof(float), (void*)(6 * sizeof(float)));
	glEnableVertexAttribArray(1);

//shader
Shader winShader("res/shader/blendVertex.shader", "res/shader/blendFragment.shader");
//texture
	unsigned int texture4;
	glGenTextures(1, &texture4);
	glBindTexture(GL_TEXTURE_2D, texture4);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

	stbi_set_flip_vertically_on_load(true);
	data = stbi_load("res/texture/blending_transparent_window.png", &width, &height, &nrChannels, 0);
	if (data)
	{
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
		glGenerateMipmap(GL_TEXTURE_2D);
	}
	else
	{
		std::cout << "Failed to load texture4." << std::endl;
	}
	stbi_image_free(data);
//windows
	vector<glm::vec3> winPositions;
	winPositions.push_back(glm::vec3(7.0, -10.0, 6.0));
	winPositions.push_back(glm::vec3(8.0, -10.0, 8.0));
	winPositions.push_back(glm::vec3(9.0, -10.0, 7.0));
//blend
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

		map<float, glm::vec3> sorted;
		for (unsigned int i = 0; i != winPositions.size(); i++)
		{
			float len = glm::length(camera.Position-winPositions[i]);
			sorted[len] = winPositions[i];
		}

		winShader.use();

		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, texture4);
		winShader.setInt("texture_win",0);

		winShader.setMat4("view", glm::value_ptr(view));
		winShader.setMat4("projection", glm::value_ptr(projection));
		for (auto i = sorted.rbegin(); i != sorted.rend(); i++)
		{
			glm::mat4 model;
			model = glm::translate(model, i->second);
			winShader.setMat4("model", glm::value_ptr(model));
			glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
		}

四、面剔除

标签:glm,04,OpenGL,高级,TEXTURE,shader,测试,深度,GL
From: https://www.cnblogs.com/etherovo/p/17398024.html

相关文章

  • NI LabVIEW OPC Server OPC通讯IO服务器,通讯西门子S720030040012001500SMART通讯三菱F
    NILabVIEWOPCServerOPC通讯IO服务器,通讯西门子S720030040012001500SMART通讯三菱FXPLCQPLC台达PLC欧姆龙PLC等全系列PLCDSCModleNIDSCOPC工具包ID:3460671157181864......
  • LabVIEW 网口通讯西门子网络通讯C#VS开源协议库西门子S7200300400SMART网口TCP通讯支
    LabVIEW网口通讯西门子网络通讯C#VS开源协议库西门子S7200300400SMART网口TCP通讯支持VSLABVIEW全系列西门子PLC通讯S7200S7300S7400SMART品种齐全ID:5899668797293613......
  • LeetCode 1047. 删除字符串中的所有相邻重复项
    题目链接:LeetCode1047.删除字符串中的所有相邻重复项题意:给出由小写字母组成的字符串S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。在S上反复执行重复项删除操作,直到无法继续删除。解题思路:开一个栈,然后扫描整个字符串。如果当前字符和栈顶元素不相等,则当前......
  • Ubuntu 22.04 停止显示 Daemons using outdated libraries
    这是Ubuntu22.04的新特性,现在是apt-get安装过程的一个步骤,是由needrestart命令触发,默认情况是交互性质的,也就是会中断在这里需要手动要处理提示。解决的方法是修改/etc/needrestart/needrestart.conf文件,将#$nrconf{restart}='i';这行去掉注释,按照需要改成以下两......
  • 04-面试必会-Redis篇
    01-你们项目中哪里用到了Redis?在我们的项目中很多地方都用到了Redis,Redis在我们的项目中主要有三个作用:使用Redis做热点数据缓存/接口数据缓存使用Redis存储一些业务数据,例如:验证码,用户信息,用户行为数据,数据计算结果,排行榜数据等使用Redis......
  • DTS104TC 数值分析
    ModulecodeandTitle   DTS104TCNumericalMethodsSchoolTitle   SchoolofArtificialIntelligenceandAdvancedComputingAssignmentTitle   Assignment1 SubmissionDeadline   June2,2023.5pm(GMT+8)FinalWordCount   -Ifyouagreetol......
  • Ubuntu 20.04 实装我的世界 Paper 服务器
    0x01-环境需求一台能上网、有公网IP的服务器ssh客户端(可选,如果是云服务器则必须)Linux使用经验(可选)0x02-下载装好系统,先更新:sudoaptupdatesudoaptupgrade去PaperMC网站选好版本,点击下载,再用ftp传到服务器。0x03-首次启动因为核心是.jar文件......
  • chat04项目代码解析
    1.@Overrideprotectedvoidconfigure(AuthenticationManagerBuilderauth)throwsException{auth.userDetailsService(userDetailsServiceImpl)//配置AuthenticationManager使用userService.passwordEncoder(passwordEncoder())//配置AuthenticationManager使用......
  • leetcode 1604. 警告一小时内使用相同员工卡大于等于三次的人
    力扣公司的员工都使用员工卡来开办公室的门。每当一个员工使用一次他的员工卡,安保系统会记录下员工的名字和使用时间。如果一个员工在一小时时间内使用员工卡的次数大于等于三次,这个系统会自动发布一个警告 。给你字符串数组 keyName 和 keyTime,其中 [keyName[i],keyTime[i......
  • CAN304 W3
    CAN304W3MessageauthenticationcodeMessageintegrity我们一直关注确保通信的保密性。Integrity:确保接收到的消息来自预期方,并且未被修改,即使攻击者控制该通道。保密(secrecy)和完整性(integrity)是正交的问题(orthogonalconcerns),可以有一个而没有另一个。加密可......