网格渲染生成地形

之前一直对图形学的mesh心存疑惑,请教师兄,做了个小demo。
这篇博客使用灵活的可编程管线,基于网格和高度图渲染地形。
思路:先用网格生成算法生成网格,这篇博客生成的是地形是在XZ平面上的,初始的Y值设置为0。在shader里面获取高度图(说到底还是灰度图)的RGB值,修改网格的Y值为正相关于高度图的R值。片段着色器设定阈值,比较它和Y值的大小,高于这个值,纹理都是山;Y值低于阈值,将两张纹理混合,生成火山的效果,低的越多熔岩效果越多。

跑出的效果图

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

在这里插入图片描述

混合效果

在这里插入图片描述

一、地形数据

在这里插入图片描述
本来有个想法就是直接用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);
    }
}

四、问题与解决手法

在这里插入图片描述
问题:边缘的网格被拉伸的很夸张。
原因:当采样纹理边缘的时候,OpenGL在边界值和下一个重复的纹理的值之间进行插值运算,而且之前纹理的过滤方式设置的是GL_REPEAT。
手法:纹理过滤方式改为GL_CLAMP_TO_EDGE。

结果变正常了:
在这里插入图片描述

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

五、纹理下载


纹理下载链接

提取码:d4dy

发布了33 篇原创文章 · 获赞 5 · 访问量 2302

猜你喜欢

转载自blog.csdn.net/cj1561435010/article/details/101623171
今日推荐