Created
October 12, 2014 18:51
-
-
Save sp4cemonkey/3373a25556dba6984124 to your computer and use it in GitHub Desktop.
Terrain heightmap
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--# Main | |
-- Terrain | |
--[[ | |
I started a project on terrains with the idea of small triangles near the camera, and then bigger triangles further away where less detail was required and driving the terrain from a heightfield. | |
It got very messy... | |
So I restarted heavily influenced by: | |
http://galfar.vevb.net/wp/2013/android-terrain-rendering-vertex-texture-fetch-part-1/ | |
In fact I got my shaders from there (although they've been modified a little since). | |
Once I had the shader I proved I could draw a mesh and heightField texture it. | |
Then I found http://www.shamusyoung.com/twentysidedtale/?p=146 which talked about precooking lighting into a texture from a heightfield. So I did that. | |
So I got it drawing a big layer of quads (all the same size at the mo) with the above texturing and it was good, but very slow. | |
So I looked on the web and found frustum culling, got that code and did sphere based culling, it was better, but still slow. | |
http://www.flipcode.com/archives/Frustum_Culling.shtml | |
http://zach.in.tu-clausthal.de/teaching/cg1_0607/literatur/lighthouse3d_view_frustum_culling/index.html | |
Then I figured out the quad thing | |
http://www.gamedev.net/page/resources/_/technical/graphics-programming-and-theory/quadtrees-r1303 | |
It was now culling quick, but still using my smallest quads across the board. Added distance based lod (level of detail) so it did small quads close and bigger quads far away. Performance was now good, but the terrain ended up with holes on the edges where lod was different between quads. | |
Went back to the original android terrain article, (and the shader already had the meat of this) and sorted out adjustments for the shader to fix the boudaries. The C++ implementation for the article performed bad in lua so adjusted it a bit. | |
]] | |
--at the moment we just draw it all... | |
--step one frustrum culling... probably take a simple bounding sphere | |
--http://www.flipcode.com/archives/Frustum_Culling.shtml | |
--http://zach.in.tu-clausthal.de/teaching/cg1_0607/literatur/lighthouse3d_view_frustum_culling/index.html | |
--for the future quad tree for frustrum culling | |
--http://www.gamedev.net/page/resources/_/technical/graphics-programming-and-theory/quadtrees-r1303 | |
--and dynamic lodding thing from http://galfar.vevb.net/wp/2013/android-terrain-rendering-vertex-texture-fetch-part-1/ | |
--tile bounding sphere info... the tileRadius and position adjustment could be pre calced for a given size | |
-- Use this function to perform your initial setup | |
function setup() | |
displayMode(FULLSCREEN) | |
heights = readImage("Dropbox:terrain_map6") | |
--cook a lighting / colouring map from the heights, inspired by http://www.shamusyoung.com/twentysidedtale/?p=147 | |
m2=mesh() | |
m2:addRect(512,512,1024,1024) | |
m2.texture = heights | |
m2.shader = shader(LightChef.Vertex, LightChef.Fragment) | |
m2.shader.heightSampleScale = 0.05/8 | |
m2.shader.resStep = 1/1024 | |
m2.shader.sunAngle = .2/1024 | |
lightMap = image(1024,1024) | |
setContext(lightMap) | |
m2:draw() | |
setContext() | |
m2 = nil | |
collectgarbage() | |
--build up a mesh for my terrain tiles | |
m = mesh() | |
v = {} | |
--the coordinate space by default of the mesh | |
coordSize = 4 | |
--tileSize is number of squares to break the mesh into... higher number is smoother but slower | |
tileSize = 8 | |
lodFactor = tileSize / 4 | |
tileWidth = coordSize / tileSize | |
for x=0, tileSize-1 do | |
for y=0, tileSize-1 do | |
buildTriangles(v, x * tileWidth, y * tileWidth, tileWidth) | |
end | |
end | |
m.vertices = v | |
--colors are only used if running it in show wireframes mode by uncommenting #define DRAW_EDGES in te vertex and fragment shader in TerrainShader tab | |
set_wireframe_colors(m) | |
--setup the main shader | |
m.shader = shader(TerrainShader.vertex, TerrainShader.fragment) | |
m.shader.tileSize = coordSize | |
m.shader.nodeScale = 1 --scales the x/z size against coordinates | |
m.shader.nodePos = vec2(0,0) --offset the position of this tile | |
m.shader.heightSampleScale = .05 --how much to scale heights against | |
m.shader.lodScales = vec4(.5,1,2,4) --whether the adjcent tiles are the same scale | |
m.shader.terrainSize = 8 | |
m.shader.texHeight = heights --heightmap | |
m.shader.texLight = lightMap --light and color map | |
--clear out the images we loaded to clear some memory | |
heights = nil | |
lightMap = nil | |
collectgarbage() | |
--for metrics | |
elapsedTimeMem = ElapsedTime | |
FPS = 0 | |
elapsedTimeFPS = ElapsedTime | |
memory = 0 | |
msg = "" | |
--set up a quad for culling | |
--1, 2, 4, 8, 16, 32, 64, 128, 256 --8 levels | |
--lodLookup is an array to find the neighbouring quad when fixing boundaries at different scales | |
lodLookup={} | |
--it'll keep breaking quads into 4 (halving scale) until starting scale (256) reaches maxDepth | |
maxDepth = 1 | |
--newQuad works recursively, this is the top quad covering the full scale*coordsize area (256*4) with an initial model coordinate (-128.5*4) and a notional quad coordinate of 1,1 | |
terrainQuad = newQuad(vec2(1,1), vec2(-128.5*4,-128.5*4) , 256) | |
--for the camera | |
camPos = vec2(4,0) | |
lookDir = vec2(0,4) | |
--not used at current, idea was to lean the camera into turns | |
sideLean = 0 | |
--initialise extra metrics | |
drawn = 0 | |
frusChecks = 0 | |
end | |
--recursive function for building up our quad tree for efficient culling and lod | |
function newQuad(quadCoord, pos, scale) | |
--create this quad, it needs a quad coordinate for lodding, a model coordinate for drawing, a mid point and radius for sphere based frustum culling, the scale, and a used flag for runtime | |
local thisQuad = {coord = quadCoord, loc = pos, mid = vec3(pos.x + (coordSize * scale/2), (36 * .05/2), pos.y + (coordSize * scale/2)), radius = math.sqrt(((coordSize * scale / 2)^2*2) + (36 * .05/2)^2), scale = scale, used = false } | |
--if we haven't reached the "bottom" then recurse in the 4 sub quads and make the current quad their parent | |
if scale > maxDepth then | |
thisQuad.subQuads = {} | |
thisQuad.subQuads[1] = newQuad(quadCoord, vec2(pos.x, pos.y), scale/2) | |
thisQuad.subQuads[1].parent = thisQuad | |
thisQuad.subQuads[2] = newQuad(quadCoord+vec2(scale/2,0), vec2(pos.x+(coordSize*scale/2), pos.y), scale/2) | |
thisQuad.subQuads[2].parent = thisQuad | |
thisQuad.subQuads[3] = newQuad(quadCoord+vec2(0,scale/2), vec2(pos.x, pos.y+(coordSize*scale/2)), scale/2) | |
thisQuad.subQuads[3].parent = thisQuad | |
thisQuad.subQuads[4] = newQuad(quadCoord+vec2(scale/2,scale/2), vec2(pos.x+(coordSize*scale/2), pos.y+(coordSize*scale/2)), scale/2) | |
thisQuad.subQuads[4].parent = thisQuad | |
end | |
--we also need to add the current quad to our lodLookup for lod neighbour checking, extra ifs to make sure the data structure is initialised at each step | |
if lodLookup[scale] == nil then | |
lodLookup[scale] = {} | |
end | |
if lodLookup[scale][quadCoord.x] == nil then | |
lodLookup[scale][quadCoord.x] = {} | |
end | |
lodLookup[scale][quadCoord.x][quadCoord.y] = thisQuad | |
return thisQuad | |
end | |
--adds a square divided into 8 triangles to a mesh | |
function buildTriangles(v, x, z, w) | |
table.insert(v, vec3(x,0,z)) | |
table.insert(v, vec3(x+w/2,0,z)) | |
table.insert(v, vec3(x+w/2,0,z+w/2)) | |
table.insert(v, vec3(x+w/2,0,z)) | |
table.insert(v, vec3(x+w,0,z)) | |
table.insert(v, vec3(x+w/2,0,z+w/2)) | |
table.insert(v, vec3(x+w,0,z)) | |
table.insert(v, vec3(x+w,0,z+w/2)) | |
table.insert(v, vec3(x+w/2,0,z+w/2)) | |
table.insert(v, vec3(x+w,0,z+w/2)) | |
table.insert(v, vec3(x+w,0,z+w)) | |
table.insert(v, vec3(x+w/2,0,z+w/2)) | |
table.insert(v, vec3(x+w,0,z+w)) | |
table.insert(v, vec3(x+w/2,0,z+w)) | |
table.insert(v, vec3(x+w/2,0,z+w/2)) | |
table.insert(v, vec3(x+w/2,0,z+w)) | |
table.insert(v, vec3(x,0,z+w)) | |
table.insert(v, vec3(x+w/2,0,z+w/2)) | |
table.insert(v, vec3(x,0,z+w)) | |
table.insert(v, vec3(x,0,z+w/2)) | |
table.insert(v, vec3(x+w/2,0,z+w/2)) | |
table.insert(v, vec3(x,0,z+w/2)) | |
table.insert(v, vec3(x,0,z)) | |
table.insert(v, vec3(x+w/2,0,z+w/2)) | |
end | |
-- This function gets called once every frame | |
function draw() | |
--some rough touch handling so we can drive around the terrain based on touch near the centre of the screen | |
if CurrentTouch.state < 2 then | |
dir = vec2(WIDTH/2-CurrentTouch.x, HEIGHT/2-CurrentTouch.y) | |
--touch to the side of middle rotates the view | |
lookDir = lookDir:rotate(-0.05 * dir.x/(WIDTH/2)*DeltaTime*60) | |
--touch up or down of middle moves forwards/backwards | |
camPos = camPos - lookDir *.02 * dir.y/(HEIGHT/2) *DeltaTime*60 | |
--attempt to do leaning, but disabled as I couldn't make the camera work right | |
sideLean = sideLean + 0.00002 * dir.x | |
end | |
background(40, 40, 50) | |
--display some metrics (FPS, memory, quads drawn and frustum checks performed) | |
metrics() | |
perspective() | |
camera(camPos.x,1.5,camPos.y,camPos.x+lookDir.x,0,camPos.y+lookDir.y,0,1,0) | |
--switching in the following camera gives a top down view useful for checking lodding in wireframe more | |
--camera(coordSize/2 ,20,coordSize/2,coordSize/2,0,coordSize/2) | |
--create our variables to express the frustum planes | |
setupFrustum() | |
--clear the metrics | |
drawn = 0 | |
frusChecks = 0 | |
--create an empty queue for the drawing cycle | |
quadQueue = {} | |
--evaluate the quads from the top level, this is a recursive function | |
--it will perform culling and lod calculations and populate the quadqueue with the quads to be drawn | |
evaluateQuad(terrainQuad) | |
--process the quadqueue and draw each one. this has to be done after the full evaluation so lod adjustments work | |
for k,v in pairs(quadQueue) do | |
--we need a lods array to feed to the shader. basically .5 means neighbour is same scale | |
--1 is double scale, 2 4x etc | |
--in the shader this messes with the triangles on the edge so the edge is lower red and you don't get gaps | |
lods = vec4(.5,.5,.5,.5) | |
lods.y = checkNeighbourLod(v.coord.x, v.coord.y-v.scale, v.scale) | |
lods.x = checkNeighbourLod(v.coord.x-v.scale, v.coord.y, v.scale) | |
lods.w = checkNeighbourLod(v.coord.x, v.coord.y+v.scale, v.scale) | |
lods.z = checkNeighbourLod(v.coord.x+v.scale, v.coord.y, v.scale) | |
--actually draw it | |
drawTile(v.scale, v.loc, lods) | |
end | |
--flatten side lean over time. unused | |
sideLean = sideLean * 0.9 | |
end | |
--This will determine what scaling the neighbouring quads are | |
--the initial approach (off the web) was a massive array, but array performance in lua is bad | |
--so it now does a lookup for the neighbouring tile, then uses the quad tree | |
function checkNeighbourLod(x,y, scale) | |
if x > 0 and y > 0 and x < 257 and y < 257 then | |
base = .5 | |
--get the neighbouring quad at the current scale | |
cquad = lodLookup[scale][x][y] | |
while (cquad.parent ~= nil) do | |
--now step up the quad tree and if it's used, that's the one | |
--it could be used at multiple levels from previous frames, but the highest | |
--up the heirarchy will be the one from this frame | |
cquad = cquad.parent | |
if cquad.used == true then | |
base = cquad.scale/scale/2 | |
end | |
end | |
return base | |
end | |
return .5 | |
end | |
--this evaluates the quad recursively to see if it needs to be drawn | |
function evaluateQuad(quad) | |
--if the quad isn't in the view frustum based on a sphere around it, then we are done | |
if isSphereInFrustum(quad.mid, quad.radius) then | |
--we are in view, get the distance from the camera to the middle of the quad | |
dist = quad.mid - vec3(camPos.x, 1.5, camPos.y) | |
--now if we are the bottom of our quad tree, or the quad is far from the camera then use it | |
--the distance from camera piece says basically use a large one if it's far away to save on drawing, as it's far away and you won't see extra triangles anyway | |
if quad.scale > maxDepth and quad.scale/(dist:len()+quad.radius/1.7) > 0.13 then | |
--it's big or too close for it's scale, so go down the next level | |
quad.used = false | |
evaluateQuad(quad.subQuads[1]) | |
evaluateQuad(quad.subQuads[2]) | |
evaluateQuad(quad.subQuads[3]) | |
evaluateQuad(quad.subQuads[4]) | |
else | |
--we are going to draw this one | |
--record we used it for lod lookups | |
quad.used = true | |
--put it in our drawing queue | |
table.insert(quadQueue, quad) | |
--increment our metric for number of quads drawn | |
drawn=drawn+1 | |
end | |
end | |
end | |
--this creates a set of variables representing the planes surrounding the frustum | |
--it bases it off the modelViewProjection matrix, and the rest of it I found on the web | |
function setupFrustum() | |
clip = modelMatrix() * viewMatrix() * projectionMatrix() | |
frustum = {} | |
--/* Extract the numbers for the RIGHT plane */ | |
frustum[0] = {} | |
frustum[0][0] = clip[ 4] - clip[ 1] | |
frustum[0][1] = clip[ 8] - clip[ 5] | |
frustum[0][2] = clip[12] - clip[ 9] | |
frustum[0][3] = clip[16] - clip[13] | |
--/* Normalize the result */ | |
t = math.sqrt( frustum[0][0] * frustum[0][0] + frustum[0][1] * frustum[0][1] + frustum[0][2] * frustum[0][2] ) | |
frustum[0][0] = frustum[0][0] / t | |
frustum[0][1] = frustum[0][1] / t | |
frustum[0][2] = frustum[0][2] / t | |
frustum[0][3] = frustum[0][3] / t | |
--/* Extract the numbers for the LEFT plane */ | |
frustum[1] = {} | |
frustum[1][0] = clip[ 4] + clip[ 1] | |
frustum[1][1] = clip[ 8] + clip[ 5] | |
frustum[1][2] = clip[12] + clip[ 9] | |
frustum[1][3] = clip[16] + clip[13] | |
--/* Normalize the result */ | |
t = math.sqrt( frustum[1][0] * frustum[1][0] + frustum[1][1] * frustum[1][1] + frustum[1][2] * frustum[1][2] ) | |
frustum[1][0] = frustum[1][0] / t | |
frustum[1][1] = frustum[1][1] / t | |
frustum[1][2] = frustum[1][2] / t | |
frustum[1][3] = frustum[1][3] / t | |
--/* Extract the BOTTOM plane */ | |
frustum[2] = {} | |
frustum[2][0] = clip[ 4] + clip[ 2]; | |
frustum[2][1] = clip[ 8] + clip[ 6]; | |
frustum[2][2] = clip[12] + clip[ 10]; | |
frustum[2][3] = clip[16] + clip[14]; | |
--/* Normalize the result */ | |
t = math.sqrt( frustum[2][0] * frustum[2][0] + frustum[2][1] * frustum[2][1] + frustum[2][2] * frustum[2][2] ) | |
frustum[2][0] = frustum[2][0] / t | |
frustum[2][1] = frustum[2][1] / t | |
frustum[2][2] = frustum[2][2] / t | |
frustum[2][3] = frustum[2][3] / t | |
--/* Extract the TOP plane */ | |
frustum[3] = {} | |
frustum[3][0] = clip[ 4] - clip[ 2]; | |
frustum[3][1] = clip[ 8] - clip[ 6]; | |
frustum[3][2] = clip[12] - clip[ 10]; | |
frustum[3][3] = clip[16] - clip[14]; | |
--/* Normalize the result */ | |
t = math.sqrt( frustum[3][0] * frustum[3][0] + frustum[3][1] * frustum[3][1] + frustum[3][2] * frustum[3][2] ) | |
frustum[3][0] = frustum[3][0] / t | |
frustum[3][1] = frustum[3][1] / t | |
frustum[3][2] = frustum[3][2] / t | |
frustum[3][3] = frustum[3][3] / t | |
--/* Extract the FAR plane */ | |
frustum[4] = {} | |
frustum[4][0] = clip[ 4] - clip[ 3]; | |
frustum[4][1] = clip[ 8] - clip[ 7]; | |
frustum[4][2] = clip[12] - clip[11]; | |
frustum[4][3] = clip[16] - clip[15]; | |
--/* Normalize the result */ | |
t = math.sqrt( frustum[4][0] * frustum[4][0] + frustum[4][1] * frustum[4][1] + frustum[4][2] * frustum[4][2] ) | |
frustum[4][0] = frustum[4][0] / t | |
frustum[4][1] = frustum[4][1] / t | |
frustum[4][2] = frustum[4][2] / t | |
frustum[4][3] = frustum[4][3] / t | |
--/* Extract the NEAR plane */ | |
frustum[5] = {} | |
frustum[5][0] = clip[ 4] + clip[ 3]; | |
frustum[5][1] = clip[ 8] + clip[ 7]; | |
frustum[5][2] = clip[12] + clip[11]; | |
frustum[5][3] = clip[16] + clip[15]; | |
--/* Normalize the result */ | |
t = math.sqrt( frustum[5][0] * frustum[5][0] + frustum[5][1] * frustum[5][1] + frustum[5][2] * frustum[5][2] ) | |
frustum[5][0] = frustum[5][0] / t | |
frustum[5][1] = frustum[5][1] / t | |
frustum[5][2] = frustum[5][2] / t | |
frustum[5][3] = frustum[5][3] / t | |
end | |
--this function checks whether a sphere at loc with a radius falls even partially in the view frustum | |
function isSphereInFrustum(loc, radius) | |
frusChecks = frusChecks + 1 | |
for p = 0,5 do | |
if ( frustum[p][0] * loc.x + frustum[p][1] * loc.y + frustum[p][2] * loc.z + frustum[p][3] <= -radius ) then | |
return false | |
end | |
end | |
return true | |
end | |
--get the shader to draw a single quad | |
function drawTile(tileSize, pos, scales) | |
m.shader.nodeScale = tileSize | |
m.shader.nodePos = pos | |
m.shader.lodScales = scales / lodFactor | |
m:draw() | |
end | |
--only used if wireframing is turned on | |
function set_wireframe_colors(m) | |
local cc={} | |
for i = 1, m.size/3 do | |
table.insert(cc, color(255,0,0)) | |
table.insert(cc, color(0,255,0)) | |
table.insert(cc, color(0,0,255)) | |
end | |
m.colors = cc | |
end | |
--print some metrics | |
function metrics() | |
if (ElapsedTime - elapsedTimeMem > 5) then | |
memory = collectgarbage("count") / 1024 | |
elapsedTimeMem = ElapsedTime | |
end | |
pushMatrix() | |
resetMatrix() | |
pushStyle() | |
fill(color(255)) | |
font("Georgia") | |
fontSize(20) | |
--fontSize(DefaultStyle.fontSize) | |
--foregroundColor(color(255)) | |
FPS = FPS * 0.9 + 0.1 / DeltaTime | |
if (ElapsedTime - elapsedTimeFPS > 0.25) then | |
msg = string.format("%.0fFPS %.1fMB %.0fTs %.0fFCs", FPS, memory, drawn, frusChecks) | |
elapsedTimeFPS = ElapsedTime | |
end | |
text(msg, WIDTH - 130, 64) | |
popStyle() | |
popMatrix() | |
end | |
--# LightShader | |
LightChef = { | |
Vertex = [[ | |
uniform mat4 modelViewProjection; | |
attribute vec4 position; | |
attribute vec2 texCoord; | |
varying highp vec2 vTexCoord; | |
void main() | |
{ | |
vTexCoord = texCoord; | |
gl_Position = modelViewProjection * position; | |
} | |
]], | |
Fragment = [[ | |
precision highp float; | |
uniform lowp sampler2D texture; | |
uniform float heightSampleScale; | |
uniform float resStep; | |
uniform float sunAngle; | |
varying highp vec2 vTexCoord; | |
void main() | |
{ | |
highp vec4 heightSample = texture2D( texture, vTexCoord ); | |
highp float mid = (heightSample.r * 25.0 + heightSample.g * 10.0 + heightSample.b) * heightSampleScale; | |
heightSample = texture2D( texture, vTexCoord + vec2(-resStep,0) ); | |
highp float left = (heightSample.r * 25.0 + heightSample.g * 10.0 + heightSample.b) * heightSampleScale; | |
heightSample = texture2D( texture, vTexCoord + vec2(resStep,0) ); | |
highp float right = (heightSample.r * 25.0 + heightSample.g * 10.0 + heightSample.b) * heightSampleScale; | |
heightSample = texture2D( texture, vTexCoord + vec2(0,-resStep) ); | |
highp float up = (heightSample.r * 25.0 + heightSample.g * 10.0 + heightSample.b) * heightSampleScale; | |
heightSample = texture2D( texture, vTexCoord + vec2(0,resStep) ); | |
highp float down = (heightSample.r * 25.0 + heightSample.g * 10.0 + heightSample.b) * heightSampleScale; | |
vec3 va = normalize( vec3(resStep, ((left+mid)-(right+mid))/2.0, 0.0) ); | |
vec3 vb = normalize( vec3(0.0, ((down+mid)-(up+mid))/2.0, -resStep) ); | |
vec3 normal = normalize( cross(va, vb) ); | |
highp vec4 col = vec4(0.0,0.0,0.0,0.0); | |
//snow is stronger at altitude | |
col.r = pow(mid / (36.0 * heightSampleScale),2.0) * 4.0; | |
//stone on steep bits | |
col.g = (1.0 - normal.y) *3.0; | |
//grass what's left | |
col.b = 1.0 - col.r - col.g; | |
//light first | |
col.a = dot(normal, normalize(vec3(resStep, sunAngle, 0.0))); | |
heightSample = texture2D( texture, vTexCoord ); | |
highp float height = (heightSample.r * 25.0 + heightSample.g * 10.0 + heightSample.b) * heightSampleScale; | |
highp float leftHeight; | |
for (highp int i=1; i< 200; i=i+5) { | |
heightSample = texture2D(texture, vTexCoord + vec2(-resStep*float(i),0)); | |
leftHeight = (heightSample.r * 25.0 + heightSample.g * 10.0 + heightSample.b) * heightSampleScale; | |
if (leftHeight > height + sunAngle * float(i) && vTexCoord.x - resStep*float(i) > 0.0) { | |
col.a = 0.0; | |
} | |
} | |
col.a = col.a + 0.2; | |
gl_FragColor = col; | |
} | |
]]} | |
--# TerrainShader | |
TerrainShader = { | |
vertex = [[ | |
//#define DRAW_EDGES | |
attribute vec4 position; | |
attribute vec3 color; | |
uniform float terrainSize; | |
uniform float tileSize; | |
uniform float nodeScale; | |
uniform vec2 nodePos; | |
uniform mat4 modelViewProjection; | |
//uniform vec3 cameraPos; | |
uniform float heightSampleScale; | |
uniform vec4 lodScales; | |
uniform sampler2D texHeight; | |
varying vec2 groundTexCoords; | |
#ifdef DRAW_EDGES | |
varying vec3 baryCoords; | |
varying vec2 nodeCoords; | |
#endif | |
const float detailScale = 100.0; | |
const float eps = 0.001; | |
float moveEdgeCoord(float edgeCoord, float lodScale) { | |
float m = mod(edgeCoord, lodScale); | |
return step(0.5, m / lodScale) * lodScale + (edgeCoord - m); | |
} | |
void main() { | |
#ifdef DRAW_EDGES | |
baryCoords = color; | |
nodeCoords = position.xz / tileSize; | |
#endif | |
vec2 p = vec2(position.x, position.z); | |
if (p.x < eps) { | |
p.y = moveEdgeCoord(p.y, lodScales[0]); | |
} else if (p.x > tileSize - eps) { | |
p.y = moveEdgeCoord(p.y, lodScales[2]); | |
} | |
if (p.y < eps) { | |
p.x = moveEdgeCoord(p.x, lodScales[1]); | |
} else if (p.y > tileSize - eps) { | |
p.x = moveEdgeCoord(p.x, lodScales[3]); | |
} | |
vec3 pos = vec3(p.x * nodeScale + nodePos.x, 0.0, p.y * nodeScale + nodePos.y); | |
groundTexCoords = vec2(pos.x / terrainSize, pos.z / terrainSize); | |
vec3 heightSample = texture2D(texHeight, groundTexCoords).rgb; | |
pos.y = (heightSample.r * 25.0 + heightSample.g * 10.0 + heightSample.b) * heightSampleScale; | |
gl_Position = modelViewProjection * vec4(pos, 1.0); | |
} | |
]] | |
, | |
fragment = [[ | |
//#define DRAW_EDGES | |
#ifdef DRAW_EDGES | |
#extension GL_OES_standard_derivatives : enable | |
#endif | |
precision mediump float; | |
uniform sampler2D texLight; | |
varying vec2 groundTexCoords; | |
#ifdef DRAW_EDGES | |
varying vec3 baryCoords; | |
varying vec2 nodeCoords; | |
const vec3 colorTriEdge = vec3(0.5, 0.7, 0.9); | |
const vec3 colorNodeEdge = vec3(1.0, 0.0, 0.0); | |
const float triEdgeWidth = 2.0; | |
const float nodeEdgeWidth = 5.0; | |
float calcTriEdgeFactor() { | |
vec3 d = fwidth(baryCoords); | |
vec3 a3 = smoothstep(vec3(0.0), d * triEdgeWidth, baryCoords); | |
return min(min(a3.x, a3.y), a3.z); | |
} | |
float calcNodeEdgeFactor() { | |
vec2 d = fwidth(nodeCoords); | |
vec2 a1 = smoothstep(vec2(0.0), d * nodeEdgeWidth, nodeCoords); | |
vec2 a2 = smoothstep(vec2(0.0), d * nodeEdgeWidth, 1.0 - nodeCoords); | |
return min(min(a1.x, a1.y), min(a2.x, a2.y)); | |
} | |
#endif | |
void main() { | |
vec4 lightLookup = texture2D(texLight, groundTexCoords); | |
vec3 color = vec3(0.29, 0.66, 0.03); | |
color = mix(color, vec3(1.0,1.0,1.0), lightLookup.r); | |
color = mix(color, vec3(0.30, 0.20, 0.0),lightLookup.g); | |
//apply lightmap | |
color = color * lightLookup.a; | |
#ifdef DRAW_EDGES | |
float triFactor = calcTriEdgeFactor(); | |
float nodeFactor = calcNodeEdgeFactor(); | |
if (nodeFactor < 0.4) | |
triFactor = 1.0; | |
vec3 triEdgeColor = mix(colorTriEdge, vec3(0.0), triFactor); | |
vec3 nodeEdgeColor = mix(colorNodeEdge, vec3(0.0), nodeFactor); | |
color = color * clamp(nodeFactor + triFactor, 0.0, 1.0) + nodeEdgeColor + triEdgeColor; | |
#endif | |
gl_FragColor.rgb = color; | |
gl_FragColor.a = 1.0; | |
} | |
]] } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment