Advertisement Jump to content
  • Advertisement

Project: The Berg

Into The Berg

Greedy Goblin


So here we are.  I'm not a seasoned blogger, or skilled game dev or any such thing.  Social media is, in my opinion, a bit of a pain, so don't be expecting regular daily updates or anything like that.  They'll happen when they happen.

This project (The Berg) is just my idea of fun in my spare time.  I didn't want to use any fancy game engines or integrate a full-blown physics engine.  The idea is to keep it retro, as simple as possible while still creating something that a) looks fairly decent and b) is fun(ish) to play.

I have no idea how this is going to turn out, or if it will ever get completed but we'll all have a good old laugh at my incompetance along the way I'm sure.  😜

Anyway, so I already got part way in to building this thing and thought 'Hey, here's a really dumb idea, why not blog about it for a laugh and waste more of my precious spare time!'.  Awesome!

So what I have so far is a simple Node/ExpressJS/ThreeJS set up in VS Code and I've created the island geometry from a 2D texture passed into a custom vertex shader which takes a flat subdivided plane and applies a displacement to each vertex based on the appropriate UV co-ords.  I want to keep a retro look and feel so this isn't some smooth terrain; it's full of lovely flat shaded triangle-y goodness.

So I created a terrain height map in Gimp (with the help of an online island generator tool for the contour ->


The real size is 2048 x 2048.  I want to create something on the larger scale here!

Once my Node/ThreeJS setup was up and running, I built a Terrain class (this is all in Javascript by the way) and set about loading the texture using the ThreeJS loaders and passing it through to my terrain vertex displacement shader.

P.S.  The terrain shader took me a while to figure out.  Initially it didn't work quite as expected but eventually I got there.  Essentially I pass the player position to the shader and only sample a smaller portion of the heighmap depending on what view distance I want.  So I'm only ever rendering a small sub-section of the heightmap which changes as the player moves.  This was easier said than done... in my head at least.

In the end it looks something like this...

	float step = viewSize / subdivisions;
	vec2 pp = vec2(playerPos.x, -playerPos.z);
	vec2 adjPlayerPos = floor(pp / step) * step;
	vec2 remainderPP = pp - adjPlayerPos;

	float uvScale = viewSize / mapSize;
	vec2 uvOffset = vec2(adjPlayerPos + (mapSize / 2.) - (viewSize / 2.)) / mapSize;
	vec2 newUV = uvOffset + uv * uvScale;

	vec4 dm = texture2D(heightMap, newUV);
	vec3 newPos = vec3( position.x - remainderPP.x, position.y - remainderPP.y, dm.r * heightScale );
	vec3 transformed = newPos;

Sheesh!  That looks... err... complicated, but it gives a great result....





Recommended Comments

I'm very interested in what you've done and how you managed to do it.  In the project I'm working on I'm creating a spherical world.  But I was thinking that perhaps I could do something similar to what you've done.  However instead of using a height map I could use RGB values to represent x,y,z coordinates for vertices for the shader code.  Do you think that is doable?

Another question, how would you go about layering different geometries that use different shaders?  I guess you did just that in your water blog post.

Can I have your code? :D 

Share this comment

Link to comment

Hey Awoken and thanks for the interest in 'The Berg'!

I've never tried this technique with a spherical world but I guess it's pretty much the same principle except you would need a curved plane or sphere rather than a flat plane.  Mapping UVs to that becomes a little more complicated I would guess.

I'm not sure how you could use a texture to represent the x,y,z coordinates for vertices to be honest as I would imagine you would need to sample every pixel of the texture on every pass.  Shaders don't like big for-loops!  (In fact if memory serves me correctly there used to be an explicit limit to the number of loops you could do on a single pass.  I wouldn't know if that's still the case).

For my water I simply use another plane mesh with a different shader.  Nothing complicated.

And lastly... can you have my code?  Sure!  Once I'm a bit further in and got something half-playable it will be on the internet for all to see... code and all!  (I can't guarantee it will be nice clean code but hey, you get what you pay for)  :D

Edited by Greedy Goblin

Share this comment

Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Advertisement
  • Advertisement
  • What is your GameDev Story?

    In 2019 we are celebrating 20 years of! Share your GameDev Story with us.

    (You must login to your account.)

  • Blog Entries

  • Similar Content

    • By silikone
      Since hardware with full support for floating point textures and frame buffers is so ubiquitous today, is there a reason to shoehorn in HDR using non-float formats? The doubling in memory usage incurred by half-floats is indeed daunting, and minifloats just don't offer the desired level of precision in many cases. Still, this could mean so little on contemporary hardware that the benefits far outweigh any potential penalties, 
    • By sdidsa
      not sure if this is the right place to ask, but i have made a certain simulation with JavaFX 3D API and it's working very well so far,
      now i wanted to implement 3D shadows and i wasn't able to find any articles or help, i had to read a lot of articles about 3D shadow mapping algorithms but i wasn't able to apply anything on my JavaFX project
      i already assumed that it's impossible but if anyone of you have tried that, let me know
    • By HolyOdin
      I have recently read an article( ,which was very informative for me。however,there weren't too much details about how the derivatives actually work by explaining them pixels by pixels under the group, only one example was shown there which was too intuitive. but when i came to other pixels in the group, things got really tricky and confusing.for better illustration of my question, Let's just take an example here, say, i have a rendertarget like this:

      According to the article,the horizontal derivative for some value in pixel[0]  is obtained by subtracting the value itself from the correspondence in pixel[1]. Since they are in the same group ,both values can be easily fetched.Great!but how about pixel[1]? if same rule is applied the horizontal derivative should be calculated base on pixel[1] itself as well as the value in pixel[2]. but at this time the two pixels are in diffrent groups! similar situations accured when you perform the vertical derivative for pixel[4]. Well, i was originally thinking maybe there was some fancy driver features that can make it happend. unfortunately the result was not what i expected after i made some experiment.
      this simple code below failed my assumption:(you can test it and see the result in shadertoy your self )
      void mainImage( out vec4 fragColor, in vec2 fragCoord ) {     int oddRow = int(fragCoord.x) % 2 == 1 ? 1 : 0; //fragCoord是屏幕空间坐标     float xDerivative = dFdx(float(oddRow));     fragColor = vec4(xDerivative,xDerivative,xDerivative, 1.0); } if my supposition is correct, the screen shoud be alternated with white and black tiles alone the horizontal direction like this:

      but the result is pure white which is surprisingly not the case.
      So,can anyone shed some light about this problem for me. i also goggled a lot of places with no luck, all of them told you the pixels were handled in such a way, but no one really focused on specific situations as i have mentioned above.I think anyone who works with ddx ddy should absolutely have an clear understanding of this.
      Much appreciation!
    • By babaliaris
      I'm currently learning how the depth testing works in OpenGL from these tutorials and the tutorial says that
      By default the depth function GL_LESS is used that discards all the fragments that have a depth value higher than or equal to the current depth buffer's value. If i guess that the depth value it the z coordinate that I pass through the vertex data, then the above statement should not be true. Fragments with small Z values should be discarded because the depth is towards the -Z axis not fragments with higher z value. Does the depth values are created somehow else by using the z coordinate of the fragment? So the depth value is a number from 0...N so lets say a fragment has a depth value of 5 and the one that is behind it has 10, the 5 will pass the test?
    • By isu diss
      How can I find collision point and normal using sat? I read that sat can do that. Please help me?
      int AABB::supportFaceCount() { // there are only three directions for every face of an AABB box. return 3; } XMVECTOR AABB::supportFaceDirection(int i) { // the three axes of an AABB box. along the x, y and z axis. static const XMVECTOR s_aabbAxes[] = { XMVectorSet(1, 0, 0, 0), XMVectorSet(0, 1, 0, 0), XMVectorSet(0, 0, 1, 0) }; return s_aabbAxes[i]; } int AABB::supportEdgeCount() { // there are only three directions for every edges of an AABB box. return 3; } XMVECTOR AABB::supportEdgeDirection(int i) { // every edge go along the x y, or z axis. static const XMVECTOR s_aabbEdges[] = { XMVectorSet(1, 0, 0, 0), XMVectorSet(0, 1, 0, 0), XMVectorSet(0, 0, 1, 0) }; return s_aabbEdges[i]; } void AABB::supportInterval(XMVECTOR direction, float& min, float& max) { XMVECTOR centre = XMVectorSet(Center[0], Center[1], Center[2], 1); // projection of the box centre float p = XMVector3Dot(centre, direction).m128_f32[0]; // projection of the box extents float rx = fabs(direction.m128_f32[0]) * Radius[0]; float ry = fabs(direction.m128_f32[1]) * Radius[1]; float rz = fabs(direction.m128_f32[2]) * Radius[2]; // the projection interval along the direction. float rb = rx + ry + rz; min = p - rb; max = p + rb; } bool ObjectsSeparatedAlongDirection(XMVECTOR& direction, AABB* a, AABB* b) { float mina, maxa; float minb, maxb; a->supportInterval(direction, mina, maxa); b->supportInterval(direction, minb, maxb); return (mina > maxb || minb > maxa); } bool ObjectsIntersected(AABB* a, AABB* b) { // test faces of A for(int i = 0; i < a->supportFaceCount(); i++) { XMVECTOR direction = a->supportFaceDirection(i); if(ObjectsSeparatedAlongDirection(direction, a, b)) return false; } // test faces of B for(int i = 0; i < b->supportFaceCount(); i++) { XMVECTOR direction = b->supportFaceDirection(i); if(ObjectsSeparatedAlongDirection(direction, a, b)) return false; } // test cross product of edges of A against edges of B. for(int i = 0; i < a->supportEdgeCount(); i++) { XMVECTOR edge_a = a->supportEdgeDirection(i); for(int j = 0; j < b->supportEdgeCount(); j++) { XMVECTOR edge_b = b->supportEdgeDirection(j); XMVECTOR direction = XMVector3Cross(edge_a, edge_b); if(ObjectsSeparatedAlongDirection(direction, a, b)) return false; } } return true; }  

Important Information

By using, you agree to our community Guidelines, Terms of Use, and Privacy Policy. is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!