OpenGL Minecraft Renderer: Chunks mehrmals gerendert?



  • Hallo Leute,

    Ich schreibe eine kleine Anwendung, die in der Lage sein soll, Minecraft-aehnliche Welten zu rendern. Das Rendern klappt auch soweit, allerdings werden Chunks (Ein Container von 16x16x16 Bloecken) aus unerfindlichen Gruenden mehrmals gerendert.

    Angenommen ich befinden mich im Chunk {1, 0, 0} (Chunkkoordinaten sind jeweils 16-Block-Schritte), dann gibt es 2 Kopien dieses Chunks. Bewege ich mich nun beispielsweise in -X Richtung, dann bewegen sich die beiden Kopien in -Y und -Z Richtung. (glaube ich, sieht zumindest so aus.)

    Ich habe jetzt schon 2 Tage lang an dem Fehler gesucht und weiss einfach nicht mehr, was ich tun soll. Daher entschuldige ich mich, wenn nun etwas viel Code folgt, ich weiss einfach nicht, wo ich noch suchen sollte, da ich jede Stelle im Code schon mindestens 5 mal angesehen habe. Wie ich sowas debuggen soll, ist mir auch nicht klar.

    Meine Vertex- und Fragment-Shader (zur Sicherheit und um den restlichen Code klarer zu machen, ich bin mir ziemlich sicher, dass der Fehler hier nicht liegt):

    #version 330
    
    uniform mat4 projection;
    uniform mat4 view;
    
    in vec4 vertexPos;
    in vec3 instancePos;
    in vec2 texCoord;
    
    smooth out vec2 texCoordFrag;
    
    mat4 translate(vec3 v)
    {
    	return mat4
    	(
    		1, 0, 0, 0,
    		0, 1, 0, 0,
    		0, 0, 1, 0,
    		v.x, v.y, v.z, 1
    	);
    }
    
    void main()
    {
    	gl_Position = projection * view * translate(instancePos) * vertexPos;
    	texCoordFrag = texCoord;
    }
    
    #version 330
    
    uniform sampler2DArray tex;
    uniform int type;
    uniform int layers;
    
    smooth in vec2 texCoordFrag;
    out vec4 color;
    
    float layerCoord(int index)
    {
    	return max(0, min(layers - 1, floor(index + 0.5f)));
    }
    
    void main()
    {
    	color = texture(tex, vec3(texCoordFrag.x, texCoordFrag.y, layerCoord(type)));
    }
    

    Dann habe ich eine Klasse RenderWindow. In dieser laeuft der ganze Render Code mit Input Handling usw. zusammen:

    struct RenderWindow : glfw::Window
    {
    	RenderWindow()
    		: Window({WINDOW_WIDTH, WINDOW_HEIGHT}, "minecraft-like rendering")
    		, shaders_(loadShadersFromFiles({ {"main.vs", ST_VERTEX}, {"main.fs", ST_FRAGMENT} }))
    		, viewU_(shaders_.uniform("view"))
    		, pos_({ 0.f, 0.f, 0.f })
    		, angleH_(deg2rad(90.f)), angleV_(deg2rad(0.f))
    		, vertexBuffer_(cubeModelVertices, sizeof cubeModelVertices, BT_ARRAY)
    		, indexBuffer_(cubeModelIndices, sizeof cubeModelIndices, BT_ELEMENT_ARRAY)
    		, typeU_(shaders_.uniform("type"))
    		, textures_(loadTextures())
    		, instancePosA_(shaders_.attribute("instancePos"))
    		, chunkProvider_(new LocalChunkProvider)
    		, renderView_(VIEW_RADIUS, *chunkProvider_)
    	{
    		glfwSetInputMode(handle(), GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
    		glfwSetInputMode(handle(), GLFW_STICKY_KEYS, GL_TRUE);
    
    		glEnable(GL_DEPTH_TEST);
    		glDepthFunc(GL_LESS);
    
    		glEnable(GL_CULL_FACE);
    
    		glClearColor(1.f, 1.f, 1.f, 1.f);
    
    		vertexBuffer_.bind();
    
    		auto vertexPosA = shaders_.attribute("vertexPos");
    		glEnableVertexAttribArray(vertexPosA);
    		glVertexAttribPointer(vertexPosA, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), 0);
    
    		auto texCoordA = shaders_.attribute("texCoord");
    		glEnableVertexAttribArray(texCoordA);
    		glVertexAttribPointer(texCoordA, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
    
    		auto proj = glm::perspective(deg2rad(60.f), WINDOW_WIDTH / (float)WINDOW_HEIGHT,  0.1f, 200.f);
    		glUniformMatrix4fv(shaders_.uniform("projection"), 1, false, &proj[0][0]);
    
    		updateView();
    
    		glUniform1i(shaders_.uniform("tex"), 0);
    		glUniform1i(shaders_.uniform("layers"), size(textureFiles));
    	}
    
    	virtual void render() override final
    	{
    		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    		for(auto& kv : renderView_.getBuffers())
    		{
    			glUniform1i(typeU_, (int)kv.first - 1);
    
    			kv.second.buffer.bind();
    
    			glEnableVertexAttribArray(instancePosA_);
    			glVertexAttribPointer(instancePosA_, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), nullptr);
    			glVertexAttribDivisor(instancePosA_, 1);
    
    			glDrawElementsInstanced(GL_TRIANGLES, size(cubeModelIndices), GL_UNSIGNED_BYTE, nullptr, kv.second.count);
    		}
    	}
    
    	virtual void handleInput(float frameTime) override final
    	{
    		if(isKeyDown(GLFW_KEY_W))
    			pos_ += forward_ * CAMERA_MOVEMENT_SPEED * frameTime;
    
    		if(isKeyDown(GLFW_KEY_S))
    			pos_ -= forward_ * CAMERA_MOVEMENT_SPEED * frameTime;
    
    		if(isKeyDown(GLFW_KEY_A))
    			pos_ -= right_ * CAMERA_MOVEMENT_SPEED * frameTime;
    
    		if(isKeyDown(GLFW_KEY_D))
    			pos_ += right_ * CAMERA_MOVEMENT_SPEED * frameTime;
    
    		if(isKeyDown(GLFW_KEY_SPACE))
    			pos_ += glm::vec3{ 0.f, 1.f, 0.f } * CAMERA_MOVEMENT_SPEED * frameTime;
    
    		if(isKeyDown(GLFW_KEY_LEFT_SHIFT))
    			pos_ -= glm::vec3{ 0.f, 1.f, 0.f } * CAMERA_MOVEMENT_SPEED * frameTime;
    
    		if(isKeyDown(GLFW_KEY_LEFT))
    			angleH_ += CAMERA_ROTATION_SPEED * frameTime;
    
    		if(isKeyDown(GLFW_KEY_RIGHT))
    			angleH_ -= CAMERA_ROTATION_SPEED * frameTime;
    
    		if(isKeyDown(GLFW_KEY_UP))
    			angleV_ += CAMERA_ROTATION_SPEED * frameTime;
    
    		if(isKeyDown(GLFW_KEY_DOWN))
    			angleV_ -= CAMERA_ROTATION_SPEED * frameTime;
    
    		angleH_ = std::fmod(angleH_, deg2rad(360.f));
    		angleV_ = clamp(angleV_, deg2rad(-89.f), deg2rad(89.f));
    
    		updateView();
    
    		if(isKeyDown(GLFW_KEY_ESCAPE))
    			glfwSetWindowShouldClose(handle(), GL_TRUE);
    	}
    
    private:
    	void updateView()
    	{
    		auto sinH = std::sin(angleH_);
    		auto sinV = std::sin(angleV_);
    		auto cosH = std::cos(angleH_);
    		auto cosV = std::cos(angleV_);
    
    		auto deg90 = deg2rad(90.f);
    
    		forward_ = glm::vec3{ cosV * sinH, sinV, cosV * cosH };
    		right_ = glm::vec3{ std::sin(angleH_ - deg90), 0.f, std::cos(angleH_ - deg90) };
    		up_ = glm::cross(right_, forward_);
    
    		auto view = glm::lookAt(pos_, pos_ + forward_, up_);
    		glUniformMatrix4fv(viewU_, 1, false, &view[0][0]);
    
    		forward_.y = 0.f;
    		forward_ = glm::normalize(forward_);
    
    		renderView_.translateTo(pos_);
    	}
    
    	ShaderProgram shaders_;
    
    	glint viewU_;
    
    	glm::vec3 pos_;
    	glm::ivec3 prevChunkPos_;
    	float angleH_, angleV_;
    	glm::vec3 forward_, right_, up_;
    
    	Buffer vertexBuffer_;
    	Buffer indexBuffer_;
    
    	glint typeU_;
    
    	TextureArray textures_;
    
    	gluint instancePosA_;
    
    	std::unique_ptr<IChunkProvider> chunkProvider_;
    	RenderView renderView_;
    };
    

    Und dann gibt es noch die Klasse RenderView. RenderView wird informiert, wenn sich die Kamera bzw der Spieler bewegt und liefert dem RenderWindow jeden Frame die Buffer, die die Positionen der Wuerfel enthalten, die dann letztendlich gerendert werden. Der Key der Buffer-Map ist einfach der Block-Typ, der gerendert werden soll. In RenderView sollen auch spaeter alle Sichtbarkeits-Tests usw. stattfinden.
    RenderView kennt einen IChunkProvider, der im Moment einfach Chunks generiert. Die generierten Chunks passen zu den Positionen, zu denen sich der Spieler bewegt. Es muss also ein Problem beim Rendern sein.

    struct BufferCountPair
    {
    	Buffer buffer;
    	int count;
    };
    
    struct RenderView
    {
    	RenderView(int viewRadius, IChunkProvider& provider)
    		: viewRadius_(viewRadius), provider_(&provider)
    	{
    		updateVisibleChunks();
    	}
    
    	void translateTo(glm::vec3 const& pos)
    	{
    		auto chunkPos = toChunkCoord(pos);
    
    		if(currentChunkPos_ == chunkPos)
    			return;
    
    		currentChunkPos_ = chunkPos;
    		updateVisibleChunks();
    	}
    
    	std::map<unsigned, BufferCountPair> const& getBuffers() const
    	{
    		return buffers_;
    	}
    
    private:
    	void updateVisibleChunks()
    	{
    		visibleChunks_.clear();
    
    		for(int i = -viewRadius_; i != viewRadius_ + 1; ++i)
    		for(int j = -viewRadius_; j != viewRadius_ + 1; ++j)
    		for(int k = -viewRadius_; k != viewRadius_ + 1; ++k)
    		{
    			auto pos = currentChunkPos_ + ChunkCoord(i, j, k);
    
    			if(auto chunk = provider_->chunkAt(pos))
    				visibleChunks_.insert(std::make_pair(pos, chunk));
    		}
    
    		updateVertexData();
    	}
    
    	void updateVertexData()
    	{
    		vertexData_.clear();
    
    		for(auto& kv : visibleChunks_)
    		{
    			for(int i = 0; i != CHUNK_SIZE; ++i)
    			for(int j = 0; j != CHUNK_SIZE; ++j)
    			for(int k = 0; k != CHUNK_SIZE; ++k)
    			{
    				auto& chunk = *kv.second;
    
    				if(chunk.blocks[i][j][k] != BlockType::NONE)
    					vertexData_[chunk.blocks[i][j][k]].push_back(fromChunkCoord(chunk.pos) + glm::vec3{ i, j, k });
    			}
    		}
    
    		updateBuffers();
    	}
    
    	void updateBuffers()
    	{
    		buffers_.clear();
    
    		for(auto& kv : vertexData_)
    		{
    			Buffer buffer(kv.second.data(), kv.second.size() * sizeof(glm::vec3), BT_ARRAY);
    			buffers_[kv.first] = { std::move(buffer), (int)kv.second.size() };
    		}
    	}
    
    	int viewRadius_;
    	IChunkProvider* provider_;
    	ChunkCoord currentChunkPos_;
    	std::unordered_map<ChunkCoord, Chunk*> visibleChunks_;
    	std::unordered_map<unsigned, std::vector<glm::vec3>> vertexData_;
    	std::map<unsigned, BufferCountPair> buffers_;
    };
    

    Die Klassen glfw::Window, Buffer, TextureArray, ShaderProgram usw. sind kleine RAII-Wrapper, die ich selbst geschrieben habe, um mir die Sachen etwas zu vereinfachen.

    Ich habe hier ein Kleines Video gemacht, das den Bug zeigt. Testweise habe ich die View-Distance auf 0 gestellt (nur Chunk, in dem man sicht befindet sichtbar) sowie die Chunk-Groesse auf 4x4x4. Man sieht die beiden anderen Chunks, sowie einen Block am Nullpunkt, der eigentlich auch nicht da sein sollte.
    http://www.youtube.com/watch?v=kxGyuhhylxU

    Ich danke schon einmal allen im Voraus, die sich Zeit fuer meinen etwas laenglichen Post mit viel Code nehmen.
    Der Kellerautomat



  • Hallo nochmal,

    Ich habe endlich den Fehler von selbst gefunden.
    Ich hatte beim glVertexAttribPointer in render() den falschen stride angegeben. Klappt nun so wie gewuenscht.

    Gruesse,
    Der Kellerautomat


Anmelden zum Antworten