310 lines
8.5 KiB
Go
310 lines
8.5 KiB
Go
package main
|
|
|
|
import (
|
|
"image"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/go-gl/mathgl/mgl32"
|
|
|
|
"github.com/faiface/pixel"
|
|
"github.com/faiface/pixel/pixelgl"
|
|
|
|
_ "image/png"
|
|
)
|
|
|
|
const (
|
|
windowWidth = 800
|
|
windowHeight = 800
|
|
// sprite tiles are squared, 64x64 size
|
|
tileSize = 64
|
|
f = 0 // floor identifier
|
|
w = 1 // wall identifier
|
|
)
|
|
|
|
var levelData = [][]uint{
|
|
{f, f, f, f, f, f}, // This row will be rendered in the lower left part of the screen (closer to the viewer)
|
|
{w, f, f, f, f, w},
|
|
{w, f, f, f, f, w},
|
|
{w, f, f, f, f, w},
|
|
{w, f, f, f, f, w},
|
|
{w, w, w, w, w, w}, // And this in the upper right
|
|
}
|
|
var win *pixelgl.Window
|
|
var canvas *pixelgl.Canvas
|
|
var offset = pixel.V(400, 325)
|
|
var floorTile, wallTile *pixel.Sprite
|
|
|
|
func installshader(c *pixelgl.Canvas, src string, uniformNameAndVar ...interface{}) {
|
|
c.SetFragmentShader(src)
|
|
for i := 0; i < len(uniformNameAndVar); i += 2 {
|
|
c.BindUniform(
|
|
uniformNameAndVar[i+0].(string),
|
|
uniformNameAndVar[i+1],
|
|
)
|
|
}
|
|
c.UpdateShader()
|
|
}
|
|
|
|
func loadPicture(path string) (pixel.Picture, error) {
|
|
file, err := os.Open(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer file.Close()
|
|
img, _, err := image.Decode(file)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return pixel.PictureDataFromImage(img), nil
|
|
}
|
|
|
|
func run() {
|
|
var err error
|
|
|
|
cfg := pixelgl.WindowConfig{
|
|
Title: "Isometric demo",
|
|
Bounds: pixel.R(0, 0, windowWidth, windowHeight),
|
|
VSync: true,
|
|
}
|
|
win, err = pixelgl.NewWindow(cfg)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
pic, err := loadPicture("castle.png")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
wallTile = pixel.NewSprite(pic, pixel.R(0, 448, tileSize, 512))
|
|
floorTile = pixel.NewSprite(pic, pixel.R(0, 128, tileSize, 192))
|
|
|
|
uResolution := mgl32.Vec2{float32(win.Bounds().W()), float32(win.Bounds().H())}
|
|
var uTime float32
|
|
var uMouse mgl32.Vec4
|
|
|
|
canvas = pixelgl.NewCanvas(win.Bounds())
|
|
wc := win.GetCanvas()
|
|
|
|
installshader(wc, embossFragmentShader,
|
|
"u_resolution", &uResolution,
|
|
"u_time", &uTime,
|
|
"u_mouse", &uMouse,
|
|
)
|
|
start := time.Now()
|
|
for !win.Closed() {
|
|
uTime = float32(time.Since(start).Seconds())
|
|
|
|
uMouse[0] = float32(win.MousePosition().X)
|
|
uMouse[1] = float32(win.MousePosition().Y)
|
|
if win.Pressed(pixelgl.MouseButton1) {
|
|
uMouse[2] = 1.0
|
|
} else {
|
|
uMouse[2] = 0.0
|
|
}
|
|
if win.Pressed(pixelgl.MouseButton2) {
|
|
uMouse[3] = 1.0
|
|
} else {
|
|
uMouse[3] = 0.0
|
|
}
|
|
|
|
canvas.Clear(pixel.RGBA{R: 0.09, G: 0.05, B: 0.09, A: 1.0})
|
|
depthSort()
|
|
canvas.Draw(win, pixel.IM.Moved(win.Bounds().Center()))
|
|
|
|
win.Update()
|
|
}
|
|
}
|
|
|
|
// Draw level data tiles to window, from farthest to closest.
|
|
// In order to achieve the depth effect, we need to render tiles up to down, being lower
|
|
// closer to the viewer (see painter's algorithm). To do that, we need to process levelData in reverse order,
|
|
// so its first row is rendered last, as OpenGL considers its origin to be in the lower left corner of the display.
|
|
func depthSort() {
|
|
for x := len(levelData) - 1; x >= 0; x-- {
|
|
for y := len(levelData[x]) - 1; y >= 0; y-- {
|
|
isoCoords := cartesianToIso(pixel.V(float64(x), float64(y)))
|
|
mat := pixel.IM.Moved(offset.Add(isoCoords))
|
|
// Not really needed, just put to show bigger blocks
|
|
mat = mat.ScaledXY(win.Bounds().Center(), pixel.V(2, 2))
|
|
tileType := levelData[x][y]
|
|
if tileType == f {
|
|
floorTile.Draw(canvas, mat)
|
|
} else {
|
|
wallTile.Draw(canvas, mat)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func cartesianToIso(pt pixel.Vec) pixel.Vec {
|
|
return pixel.V((pt.X-pt.Y)*(tileSize/2), (pt.X+pt.Y)*(tileSize/4))
|
|
}
|
|
|
|
func main() {
|
|
pixelgl.Run(run)
|
|
}
|
|
|
|
var embossFragmentShader = `
|
|
#version 330 core
|
|
|
|
// Using a sobel filter to create a normal map and then applying simple lighting.
|
|
|
|
// This makes the darker areas less bumpy but I like it
|
|
#define USE_LINEAR_FOR_BUMPMAP
|
|
|
|
//#define SHOW_NORMAL_MAP
|
|
//#define SHOW_ALBEDO
|
|
|
|
in vec2 texcoords;
|
|
|
|
uniform vec4 u_texbounds;
|
|
uniform sampler2D u_texture;
|
|
uniform vec2 u_resolution;
|
|
uniform float u_time;
|
|
uniform vec4 u_mouse;
|
|
|
|
out vec4 fragColor;
|
|
|
|
struct C_Sample
|
|
{
|
|
vec3 vAlbedo;
|
|
vec3 vNormal;
|
|
};
|
|
|
|
C_Sample SampleMaterial(const in vec2 uv, sampler2D sampler, const in vec2 vTextureSize, const in float fNormalScale)
|
|
{
|
|
C_Sample result;
|
|
|
|
vec2 vInvTextureSize = vec2(1.0) / vTextureSize;
|
|
|
|
vec3 cSampleNegXNegY = texture(sampler, uv + (vec2(-1.0, -1.0)) * vInvTextureSize.xy).rgb;
|
|
vec3 cSampleZerXNegY = texture(sampler, uv + (vec2( 0.0, -1.0)) * vInvTextureSize.xy).rgb;
|
|
vec3 cSamplePosXNegY = texture(sampler, uv + (vec2( 1.0, -1.0)) * vInvTextureSize.xy).rgb;
|
|
|
|
vec3 cSampleNegXZerY = texture(sampler, uv + (vec2(-1.0, 0.0)) * vInvTextureSize.xy).rgb;
|
|
vec3 cSampleZerXZerY = texture(sampler, uv + (vec2( 0.0, 0.0)) * vInvTextureSize.xy).rgb;
|
|
vec3 cSamplePosXZerY = texture(sampler, uv + (vec2( 1.0, 0.0)) * vInvTextureSize.xy).rgb;
|
|
|
|
vec3 cSampleNegXPosY = texture(sampler, uv + (vec2(-1.0, 1.0)) * vInvTextureSize.xy).rgb;
|
|
vec3 cSampleZerXPosY = texture(sampler, uv + (vec2( 0.0, 1.0)) * vInvTextureSize.xy).rgb;
|
|
vec3 cSamplePosXPosY = texture(sampler, uv + (vec2( 1.0, 1.0)) * vInvTextureSize.xy).rgb;
|
|
|
|
// convert to linear
|
|
vec3 cLSampleNegXNegY = cSampleNegXNegY * cSampleNegXNegY;
|
|
vec3 cLSampleZerXNegY = cSampleZerXNegY * cSampleZerXNegY;
|
|
vec3 cLSamplePosXNegY = cSamplePosXNegY * cSamplePosXNegY;
|
|
|
|
vec3 cLSampleNegXZerY = cSampleNegXZerY * cSampleNegXZerY;
|
|
vec3 cLSampleZerXZerY = cSampleZerXZerY * cSampleZerXZerY;
|
|
vec3 cLSamplePosXZerY = cSamplePosXZerY * cSamplePosXZerY;
|
|
|
|
vec3 cLSampleNegXPosY = cSampleNegXPosY * cSampleNegXPosY;
|
|
vec3 cLSampleZerXPosY = cSampleZerXPosY * cSampleZerXPosY;
|
|
vec3 cLSamplePosXPosY = cSamplePosXPosY * cSamplePosXPosY;
|
|
|
|
// Average samples to get albdeo colour
|
|
result.vAlbedo = ( cLSampleNegXNegY + cLSampleZerXNegY + cLSamplePosXNegY
|
|
+ cLSampleNegXZerY + cLSampleZerXZerY + cLSamplePosXZerY
|
|
+ cLSampleNegXPosY + cLSampleZerXPosY + cLSamplePosXPosY ) / 9.0;
|
|
|
|
vec3 vScale = vec3(0.3333);
|
|
|
|
#ifdef USE_LINEAR_FOR_BUMPMAP
|
|
|
|
float fSampleNegXNegY = dot(cLSampleNegXNegY, vScale);
|
|
float fSampleZerXNegY = dot(cLSampleZerXNegY, vScale);
|
|
float fSamplePosXNegY = dot(cLSamplePosXNegY, vScale);
|
|
|
|
float fSampleNegXZerY = dot(cLSampleNegXZerY, vScale);
|
|
float fSampleZerXZerY = dot(cLSampleZerXZerY, vScale);
|
|
float fSamplePosXZerY = dot(cLSamplePosXZerY, vScale);
|
|
|
|
float fSampleNegXPosY = dot(cLSampleNegXPosY, vScale);
|
|
float fSampleZerXPosY = dot(cLSampleZerXPosY, vScale);
|
|
float fSamplePosXPosY = dot(cLSamplePosXPosY, vScale);
|
|
|
|
#else
|
|
|
|
float fSampleNegXNegY = dot(cSampleNegXNegY, vScale);
|
|
float fSampleZerXNegY = dot(cSampleZerXNegY, vScale);
|
|
float fSamplePosXNegY = dot(cSamplePosXNegY, vScale);
|
|
|
|
float fSampleNegXZerY = dot(cSampleNegXZerY, vScale);
|
|
float fSampleZerXZerY = dot(cSampleZerXZerY, vScale);
|
|
float fSamplePosXZerY = dot(cSamplePosXZerY, vScale);
|
|
|
|
float fSampleNegXPosY = dot(cSampleNegXPosY, vScale);
|
|
float fSampleZerXPosY = dot(cSampleZerXPosY, vScale);
|
|
float fSamplePosXPosY = dot(cSamplePosXPosY, vScale);
|
|
|
|
#endif
|
|
|
|
// Sobel operator - http://en.wikipedia.org/wiki/Sobel_operator
|
|
|
|
vec2 vEdge;
|
|
vEdge.x = (fSampleNegXNegY - fSamplePosXNegY) * 0.25
|
|
+ (fSampleNegXZerY - fSamplePosXZerY) * 0.5
|
|
+ (fSampleNegXPosY - fSamplePosXPosY) * 0.25;
|
|
|
|
vEdge.y = (fSampleNegXNegY - fSampleNegXPosY) * 0.25
|
|
+ (fSampleZerXNegY - fSampleZerXPosY) * 0.5
|
|
+ (fSamplePosXNegY - fSamplePosXPosY) * 0.25;
|
|
|
|
result.vNormal = normalize(vec3(vEdge * fNormalScale, 1.0));
|
|
|
|
return result;
|
|
}
|
|
|
|
void main()
|
|
{
|
|
vec2 uv = gl_FragCoord.xy / u_resolution.xy;
|
|
|
|
C_Sample materialSample;
|
|
|
|
float fNormalScale = 5.0;
|
|
materialSample = SampleMaterial( uv, u_texture, u_resolution.xy, fNormalScale );
|
|
|
|
// Random Lighting...
|
|
|
|
float fLightHeight = 0.2;
|
|
float fViewHeight = 1.0;
|
|
|
|
vec3 vSurfacePos = vec3(uv, 0.0);
|
|
|
|
vec3 vViewPos = vec3(0.5, 0.5, fViewHeight);
|
|
|
|
vec3 vLightPos = vec3( vec2(sin(u_time),cos(u_time)) * 0.25 + 0.5 , fLightHeight);
|
|
|
|
if( u_mouse.z > 0.0 )
|
|
{
|
|
vLightPos = vec3(u_mouse.xy / u_resolution.xy, fLightHeight);
|
|
}
|
|
|
|
vec3 vDirToView = normalize( vViewPos - vSurfacePos );
|
|
vec3 vDirToLight = normalize( vLightPos - vSurfacePos );
|
|
|
|
float fNDotL = clamp( dot(materialSample.vNormal, vDirToLight), 0.0, 1.0);
|
|
float fDiffuse = fNDotL;
|
|
|
|
vec3 vHalf = normalize( vDirToView + vDirToLight );
|
|
float fNDotH = clamp( dot(materialSample.vNormal, vHalf), 0.0, 1.0);
|
|
float fSpec = pow(fNDotH, 10.0) * fNDotL * 0.5;
|
|
|
|
vec3 vResult = materialSample.vAlbedo * fDiffuse + fSpec;
|
|
|
|
vResult = sqrt(vResult);
|
|
|
|
#ifdef SHOW_NORMAL_MAP
|
|
vResult = materialSample.vNormal * 0.5 + 0.5;
|
|
#endif
|
|
|
|
#ifdef SHOW_ALBEDO
|
|
vResult = sqrt(materialSample.vAlbedo);
|
|
#endif
|
|
|
|
fragColor = vec4(vResult,1.0);
|
|
}
|
|
`
|