网格渲染生成地形(OpenGL)

       之前一直对图形学的mesh心存疑惑,请教师兄,做了个小demo。

       这篇博客使用灵活的可编程管线,基于网格和高度图渲染地形

       思路:先用网格生成算法生成网格,这篇博客生成的是地形是在XZ平面上的,初始的Y值设置为0。在shader里面获取高度图(说到底还是灰度图)的RGB值,修改网格的Y值为正相关于高度图的R值。片段着色器设定阈值,比较它和Y值的大小,高于这个值,纹理都是山;Y值低于阈值,将两张纹理混合,生成火山的效果,低的越多熔岩效果越多。


一、效果图

1、纯火山的效果,阈值设的和高度值差很多

Alt

2、混合效果

Alt




二、地形数据

Alt


       本来有个想法就是直接用Qt的Qimage类逐像素地读取出这个高度图,然后push到自己的网格里面,直接生成具有高度的网格数据,这样不用在着色器里面修改高度值了。不过其实这里有个坑,我网上找的图片都是上面图片的jpg格式,其实他本来应该是png格式,被强转了,导致load加载出来的图片是null,后来经过一顿操作,美图秀秀直接转格式(这个不同于改后缀名,强改后缀名没有把里面数据的存放方式改掉,但是美图秀秀保存时候选成png格式却可以)。不过有些图片强转之后确实也可以load加载出来,原因应该是那些图片的像素存放比较有特点,转换前和转换后两种图片格式里面数据的存放方式本来就差不多,当然也就可以load。
最后还是用文章开头讲的思路,在着色器里面修改Y值。



三、 网格算法

       两个嵌套for循环生成,没啥神奇的地方。

       生成顶点数组的代码(pos+texcoord):

	//row_num表示网格行数,col_num表示网格列数
	int row_num=200,col_num=200;
	std::vector<float> p;
	float x=-50,z=-50;
	for(int i=0;i<row_num;i++) {
		x=-5;
		for(int j=0;j<col_num;j++) {
			p.push_back(x);
			p.push_back(0);
			p.push_back(z);
			p.push_back(1.0f/col_num*j);
			p.push_back(1-i*1.0f/row_num);
			x+=0.1;
		}
		z+=0.1;
	}

       生成索引数组的代码(绘制的图元是GL_TRIANGLES):

	std::vector<unsigned int> indicess;
	for(int i=1;i<row_num;i++) {
		for(int j=1;j<col_num;j++) {
			indicess.push_back((i-1)*col_num+j-1);
			indicess.push_back((i-1)*col_num+j);
			indicess.push_back(i*col_num+j-1);

			indicess.push_back(i*col_num+j-1);
			indicess.push_back((i-1)*col_num+j);
			indicess.push_back(i*col_num+j);
		}
	}




四、代码

       main.cpp:

	...
	//创建openGL的窗体代码
	...
	//将顶点数组(包含了texcoord)和索引数组发到GPU上
	...
	//加载纹理,创建shader并连接到程序上
	unsigned int tex1=loadTexture("rock.jpg");
	unsigned int tex2=loadTexture("water_meitu_5.jpg");
	unsigned int dep=loadTexture("heightmap.png");
	Shader shader("normal.vs","normal.fs");
	shader.use();
	shader.setInt("dep",0);
	shader.setInt("tex1",1);
	shader.setInt("tex2",2);
	...
	//render代码块
	glm::mat4 Model,View,Projection;
	while (!glfwWindowShouldClose(window))
	{
		float currentFrame = glfwGetTime();
		deltaTime = currentFrame - lastFrame;
		lastFrame = currentFrame;
		
		processInput(window);
		glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);				
		
		shader.use();
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, dep);
		glActiveTexture(GL_TEXTURE1);
		glBindTexture(GL_TEXTURE_2D, tex1);
		glActiveTexture(GL_TEXTURE2);
		glBindTexture(GL_TEXTURE_2D, tex2);
		Projection=glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
		View=camera.GetViewMatrix();
		shader.setMat4("projection", Projection);
		shader.setMat4("view", View);

		glBindVertexArray(VAO);
		Model=glm::mat4(1.0f);
		Model=glm::translate(Model, glm::vec3(0.0f, 0.0f, -2.0f)); 
		shader.setMat4("model", Model);
		glDrawElements(GL_TRIANGLES,(row_num-1)*(col_num-1)*6, GL_UNSIGNED_INT, 0);

		glfwSwapBuffers(window);
		glfwPollEvents();
	}
	
	
	

       顶点着色器代码:

	#version 330 core
	layout (location = 0) in vec3 position;
	layout (location = 1) in vec2 texcoord;
	
	uniform mat4 projection;
	uniform mat4 view;
	uniform mat4 model;
	uniform sampler2D tex1;
	uniform sampler2D tex2;
	uniform sampler2D dep;
	out vec2 coord;
	out vec3 n_p;
	
	void main()
	{
	    float height=texture(dep,texcoord).r;
		vec3 new_pos=vec3(position.x,height*5,position.z);
		gl_Position = projection*view*model*vec4(new_pos, 1.0f);
		n_p=new_pos;
		coord=texcoord;
	}


       片段着色器代码:

	#version 330 core
	out vec4 FragColor;
	
	in vec2 coord;
	uniform sampler2D tex1;
	uniform sampler2D tex2;
	uniform sampler2D dep;
	in vec3 n_p;
	
	void main()
	{           
		int m=2;
		if(n_p.y>m)
	    {
	        FragColor=texture2D(tex1,coord);
	    }
	    else
	    {
	        float alpha=n_p.y/(m);  // 比例系数
	        FragColor=texture2D(tex1,coord)*alpha+texture2D(tex2,coord)*(1-alpha);
	    }
	}



五、问题与解决手法

Alt
       问题:边缘的网格被拉伸的很夸张。

       原因:当采样纹理边缘的时候,OpenGL在边界值和下一个重复的纹理的值之间进行插值运算,而且之前纹理的过滤方式设置的是GL_REPEAT。

       手法:纹理过滤方式改为GL_CLAMP_TO_EDGE

       结果变正常了:

Alt

有一张高度图就能直接render出地形,真刺激!



六、纹理下载


纹理下载链接        提取码:d4dy
评论 42
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值